PR middle-end/88226 - missing warning on fprintf, fputs, and puts with an unterminated array

gcc/ChangeLog:

	PR middle-end/88226
	* builtins.c (check_nul_terminated_array): New function.
	(fold_builtin_0): Remove declaration.
	(fold_builtin_1): Same.
	(fold_builtin_2): Same.
	(fold_builtin_3): Same.
	(fold_builtin_strpbrk): Add argument.
	(fold_builtin_strspn): Same.
	(fold_builtin_strcspn): Same.
	(expand_builtin_strcat): Call it.  Remove unused argument.
	(expand_builtin_stpncpy): Same.
	(expand_builtin_strncat): Same.
	(expand_builtin_strncpy): Same.  Adjust indentation.
	(expand_builtin_strcmp): Same.
	(expand_builtin_strncmp): Same.
	(expand_builtin_fork_or_exec): Same.
	(expand_builtin): Handle more built-ins.
	(fold_builtin_2): Add argument.
	(fold_builtin_n): Make static.  Add argument.
	(fold_call_expr): Pass new argument to fold_builtin_n and fold_builtin_2.
	(fold_builtin_call_array): Pass new argument to fold_builtin_n.
	(fold_builtin_strpbrk): Add argument.  Call check_nul_terminated_array.
	(fold_call_stmt): Pass new argument to fold_builtin_n.
	* builtins.h: Correct a comment.
	* gimple-fold.c (gimple_fold_builtin_strchr): Call
	check_nul_terminated_array.
	* tree-ssa-strlen.c (handle_builtin_strlen): Call
	check_nul_terminated_array.
	(handle_builtin_strchr): Same.
	(handle_builtin_string_cmp): Same.

gcc/testsuite/ChangeLog:
	PR middle-end/88226
	* gcc.dg/Wstringop-overflow-22.c: New test.
	* gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Remove xfails.

Index: gcc/builtins.c
===================================================================
--- gcc/builtins.c	(revision 278253)
+++ gcc/builtins.c	(working copy)
@@ -131,7 +131,7 @@ static rtx expand_builtin_memory_copy_args (tree d
 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);
@@ -166,15 +166,11 @@ static tree fold_builtin_fabs (location_t, tree, t
 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,
@@ -564,6 +560,51 @@ warn_string_no_nul (location_t loc, const char *fn
     }
 }
 
+/* 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
@@ -4023,7 +4064,7 @@ expand_movstr (tree dest, tree src, rtx target, me
    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)
@@ -4032,6 +4073,10 @@ static rtx
   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
@@ -4227,6 +4272,8 @@ expand_builtin_stpncpy (tree exp, rtx)
 
   /* 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);
@@ -4329,6 +4376,11 @@ expand_builtin_strncat (tree exp, rtx)
   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);
 
@@ -4390,59 +4442,63 @@ expand_builtin_strncpy (tree exp, rtx target)
 {
   location_t loc = EXPR_LOCATION (exp);
 
-  if (validate_arglist (exp,
- 			POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+  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 (!check_nul_terminated_array (exp, src, len))
+    return NULL_RTX;
+
+  /* The length of the source sequence.  */
+  tree slen = c_strlen (src, 1);
+
+  if (warn_stringop_overflow)
     {
-      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);
+      tree destsize = compute_objsize (dest,
+				       warn_stringop_overflow - 1);
 
-      if (warn_stringop_overflow)
-	{
-	  tree destsize = compute_objsize (dest,
-					   warn_stringop_overflow - 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);
+    }
 
-	  /* 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 must be passed a constant len and src parameter.  */
+  if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
+    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;
+  slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1));
 
-      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;
 
-      /* 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;
 
-	  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;
+    }
 
-	  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;
 }
 
@@ -4821,6 +4877,13 @@ expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED
   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);
@@ -4832,9 +4895,6 @@ expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED
   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;
 
@@ -4940,6 +5000,14 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED
  			 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);
@@ -4955,10 +5023,6 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED
 
   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;
 
@@ -5953,6 +6017,26 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rt
   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;
@@ -7603,11 +7687,49 @@ expand_builtin (tree exp, rtx target, rtx subtarge
       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)
@@ -9670,11 +9792,12 @@ fold_builtin_1 (location_t loc, tree fndecl, tree
 
 }
 
-/* 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);
@@ -9702,13 +9825,13 @@ static tree
       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);
@@ -9826,13 +9949,14 @@ fold_builtin_3 (location_t loc, tree fndecl,
   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;
 
@@ -9845,7 +9969,7 @@ fold_builtin_3 (location_t loc, tree fndecl,
       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]);
@@ -9943,7 +10067,7 @@ fold_call_expr (location_t loc, tree exp, bool ign
       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;
 	}
@@ -9981,7 +10105,7 @@ fold_builtin_call_array (location_t loc, tree,
       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;
@@ -10133,51 +10257,53 @@ readonly_data_expr (tree exp)
    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
@@ -10199,23 +10325,25 @@ static tree
    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
@@ -10237,38 +10365,40 @@ static tree
    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
@@ -11111,7 +11241,7 @@ fold_call_stmt (gcall *stmt, bool ignore)
         }
       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
Index: gcc/builtins.h
===================================================================
--- gcc/builtins.h	(revision 278253)
+++ gcc/builtins.h	(working copy)
@@ -91,7 +91,7 @@ struct c_strlen_data
   tree minlen;
   tree maxlen;
   tree maxbound;
-  /* When non-null, NONSTR refers to the declaration known to store
+  /* When non-null, DECL refers to the declaration known to store
      an unterminated constant character array, as in:
      const char s[] = { 'a', 'b', 'c' };
      It is used to diagnose uses of such arrays in functions such as
@@ -125,7 +125,6 @@ extern tree fold_builtin_expect (location_t, tree,
 extern bool avoid_folding_inline_builtin (tree);
 extern tree fold_call_expr (location_t, tree, bool);
 extern tree fold_builtin_call_array (location_t, tree, tree, int, tree *);
-extern tree fold_builtin_n (location_t, tree, tree *, int, bool);
 extern bool validate_gimple_arglist (const gcall *, ...);
 extern rtx default_expand_builtin (tree, rtx, rtx, machine_mode, int);
 extern bool fold_builtin_next_arg (tree, bool);
@@ -148,6 +147,7 @@ extern bool target_char_cst_p (tree t, char *p);
 extern internal_fn associated_internal_fn (tree);
 extern internal_fn replacement_internal_fn (gcall *);
 
+bool check_nul_terminated_array (tree, tree, tree = NULL_TREE);
 extern void warn_string_no_nul (location_t, const char *, tree, tree);
 extern tree unterminated_array (tree, tree * = NULL, bool * = NULL);
 extern bool builtin_with_linkage_p (tree);
Index: gcc/gimple-fold.c
===================================================================
--- gcc/gimple-fold.c	(revision 278253)
+++ gcc/gimple-fold.c	(working copy)
@@ -1899,6 +1899,11 @@ gimple_fold_builtin_strchr (gimple_stmt_iterator *
   if (!gimple_call_lhs (stmt))
     return false;
 
+  /* Avoid folding if the first argument is not a nul-terminated array.
+     Defer warning until later.  */
+  if (!check_nul_terminated_array (NULL_TREE, str))
+    return false;
+
   if ((p = c_getstr (str)) && target_char_cst_p (c, &ch))
     {
       const char *p1 = is_strrchr ? strrchr (p, ch) : strchr (p, ch);
@@ -1973,18 +1978,23 @@ static bool
 gimple_fold_builtin_strstr (gimple_stmt_iterator *gsi)
 {
   gimple *stmt = gsi_stmt (*gsi);
+  if (!gimple_call_lhs (stmt))
+    return false;
+
   tree haystack = gimple_call_arg (stmt, 0);
   tree needle = gimple_call_arg (stmt, 1);
-  const char *p, *q;
 
-  if (!gimple_call_lhs (stmt))
+  /* Avoid folding if either argument is not a nul-terminated array.
+     Defer warning until later.  */
+  if (!check_nul_terminated_array (NULL_TREE, haystack)
+      || !check_nul_terminated_array (NULL_TREE, needle))
     return false;
 
-  q = c_getstr (needle);
+  const char *q = c_getstr (needle);
   if (q == NULL)
     return false;
 
-  if ((p = c_getstr (haystack)))
+  if (const char *p = c_getstr (haystack))
     {
       const char *r = strstr (p, q);
 
Index: gcc/testsuite/gcc.dg/Wstringop-overflow-22.c
===================================================================
--- gcc/testsuite/gcc.dg/Wstringop-overflow-22.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/Wstringop-overflow-22.c	(working copy)
@@ -0,0 +1,259 @@
+/* { 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.  */
+T (strcmp, a, b + 1);       // { dg-warning "missing terminating nul" "strcmp" }
+
+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);
+T (strncmp, a, a, 5);       // { dg-warning "missing terminating nul" "strcmp" }
+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" }
Index: gcc/tree-ssa-strlen.c
===================================================================
--- gcc/tree-ssa-strlen.c	(revision 278253)
+++ gcc/tree-ssa-strlen.c	(working copy)
@@ -1946,8 +1946,6 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
 static void
 handle_builtin_strchr (gimple_stmt_iterator *gsi)
 {
-  int idx;
-  tree src;
   gimple *stmt = gsi_stmt (*gsi);
   tree lhs = gimple_call_lhs (stmt);
 
@@ -1957,8 +1955,14 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi)
   if (!integer_zerop (gimple_call_arg (stmt, 1)))
     return;
 
-  src = gimple_call_arg (stmt, 0);
-  idx = get_stridx (src);
+  tree src = gimple_call_arg (stmt, 0);
+
+  /* Avoid folding if the first argument is not a nul-terminated array.
+     Defer warning until later.  */
+  if (!check_nul_terminated_array (NULL_TREE, src))
+    return;
+
+  int idx = get_stridx (src);
   if (idx)
     {
       strinfo *si = NULL;
@@ -3794,11 +3833,11 @@ handle_builtin_string_cmp (gimple_stmt_iterator *g
 
   /* For strncmp set to the the value of the third argument if known.  */
   HOST_WIDE_INT bound = -1;
-
+  tree len = NULL_TREE;
   /* Extract the strncmp bound.  */
   if (gimple_call_num_args (stmt) == 3)
     {
-      tree len = gimple_call_arg (stmt, 2);
+      len = gimple_call_arg (stmt, 2);
       if (tree_fits_shwi_p (len))
         bound = tree_to_shwi (len);
 
@@ -3807,6 +3846,12 @@ handle_builtin_string_cmp (gimple_stmt_iterator *g
 	return false;
     }
 
+  /* Avoid folding if either argument is not a nul-terminated array.
+     Defer warning until later.  */
+  if (!check_nul_terminated_array (NULL_TREE, arg1, len)
+      || !check_nul_terminated_array (NULL_TREE, arg2, len))
+    return false;
+
   {
     /* Set to the length of one argument (or its complement if it's
        the lower bound of a range) and the size of the array storing
Index: gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c	(revision 278253)
+++ gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c	(working copy)
@@ -84,8 +84,8 @@ void test_fprintf_s_const (int width)
   if (nulptr)
     T ("%s", nulptr);
 
-  T ("%s", &chr_no_nul);          /* { dg-warning ".%s. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */
-  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */
+  T ("%s", &chr_no_nul);          /* { dg-warning ".%s. directive argument is not a nul-terminated string|argument missing terminating nul" } */
+  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string|argument missing terminating nul" } */
 
   /* Verify that output in excess of INT_MAX bytes is diagnosed even
      when the size of the destination object is unknown.  */
@@ -117,7 +117,7 @@ void test_fprintf_ls_const (int width)
     T ("%ls", nulptr);
 
   T ("%ls", &wchr_no_nul);        /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
-  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr88211" { xfail *-*-* } } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */
 
   /* Verify that output in excess of INT_MAX bytes is diagnosed even
      when the size of the destination object is unknown.  */
