diff mbox

add missing attribute nonnull to stdio functions (PR 78673 and 17308)

Message ID 427c4e0d-6437-98ab-a7ee-f0e3647192bb@gmail.com
State New
Headers show

Commit Message

Martin Sebor Dec. 8, 2016, 3:24 a.m. UTC
>> +bitmap

>> +get_nonnull_args (const_tree callexpr)

>> +{

>> +  tree fn = CALL_EXPR_FN (callexpr);

>> +  if (!fn || TREE_CODE (fn) != ADDR_EXPR)

>> +    return NULL;

>> +

>> +  tree fndecl = TREE_OPERAND (fn, 0);

>> +  tree fntype = TREE_TYPE (fndecl);

>> +  tree attrs = TYPE_ATTRIBUTES (fntype);

>> +  if (!attrs)

>> +    return NULL;

>> +

>> +  attrs = lookup_attribute ("nonnull", attrs);

>> +  if (!attrs)

>> +    return NULL;

>> +

>> +  /* Return an empty but non-null bitmap as an indication that all

>> +     of the function's arguments must be non-null.  */

>> +  bitmap argmap = BITMAP_ALLOC (NULL);

>> +  if (!TREE_VALUE (attrs))

>> +    return argmap;

>> +

>> +  /* Iterate over the indices of the format arguments declared nonnull

>> +     and set a bit for each.  */

>> +  for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))

>> +    {

>> +      unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;

>> +      bitmap_set_bit (argmap, val);

>> +    }

>> +

>> +  return argmap;

>> +}

> I'm not entirely sure you're using lookup_attribute properly here.  The

> docs for that function make me believe you need to call back into

> lookup_attribute with the TREE_CHAIN of the previous return value to

> find the next occurrence.


You're right!  Good chatch! I missed that there are two ways to
represent the same thing.  For example, these two declarations

   void __attribute ((nonnull (1, 2)))
   f (void);

   void __attribute ((nonnull (1))) __attribute ((nonnull (2)))
   f (void);

apply to the same arguments but each is represented differently,
as is this one:

   void __attribute ((nonnull))
   f (void);

The builtins use the first form and I didn't have tests for user-
defined attributes in the second form.  I've fixed that in the
attached updated patch (and added more tests).  It does seem,
though, that it would be better to represent these declarations
canonically.  It would simplify the processing and avoid bugs.

Thanks
Martin

Comments

Jeff Law Dec. 14, 2016, 6:21 a.m. UTC | #1
On 12/07/2016 08:24 PM, Martin Sebor wrote:

>

> You're right!  Good chatch! I missed that there are two ways to

> represent the same thing.  For example, these two declarations

>

>   void __attribute ((nonnull (1, 2)))

>   f (void);

>

>   void __attribute ((nonnull (1))) __attribute ((nonnull (2)))

>   f (void);

>

> apply to the same arguments but each is represented differently,

> as is this one:

>

>   void __attribute ((nonnull))

>   f (void);

>

> The builtins use the first form and I didn't have tests for user-

> defined attributes in the second form.  I've fixed that in the

> attached updated patch (and added more tests).  It does seem,

> though, that it would be better to represent these declarations

> canonically.  It would simplify the processing and avoid bugs.

I'm not sure what the historical context is for having two forms of the 
same thing.  Though I would generally agree that there should be a 
canonical form.

One more high level note.  While I am in favor of adding these 
attributes, they can cause interesting codegen issues that we need to be 
on the lookout for.

Specifically when we see an SSA_NAME as an argument when the argument is 
marked as NONNULL, the optimizers will try to use that information to 
eliminate unnecessary NULL pointer checks.

The canonical example has been

memcpy (src, dst, 0)
[ stuff ]
if (src == 0)
   whatever

GCC can sometimes follow things well enough to realize that the test, in 
a conforming program, will always be false and remove the test. Some 
have claimed this is undesirable behavior on GCC's part (and they may be 
right).

Anyway, something to keep in mind.  These changes may make GCC ever so 
slightly more aggressive in its NULL pointer eliminations.


>

> Thanks

> Martin

>

> gcc-78673.diff

>

>

> PR c/78673 - sprintf missing attribute nonnull on destination argument

> PR c/17308 - nonnull attribute not as useful as it could be

>

> gcc/ChangeLog:

>

> 	PR c/78673

> 	PR c/17308

> 	* builtin-attrs.def (ATTR_NONNULL_1_1, ATTR_NONNULL_1_2): Defined.

> 	(ATTR_NONNULL_1_3, ATTR_NONNULL_1_4, ATTR_NONNULL_1_5): Same.

> 	(ATTR_NOTHROW_NONNULL_1_1, ATTR_NOTHROW_NONNULL_1_2): Same.

> 	(ATTR_NOTHROW_NONNULL_1_3, ATTR_NOTHROW_NONNULL_1_4): Same.

> 	(ATTR_NOTHROW_NONNULL_1_5): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_1_2): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_2_0): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_2_3): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_3_0): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_3_4): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_4_0): Same.

> 	(ATTR_NONNULL_1_FORMAT_PRINTF_4_5): Same.

> 	* builtins.c (validate_arg): Add argument.  Treat null pointers

> 	passed to nonnull arguments as invalid.

> 	(validate_arglist): Same.

> 	* builtins.def (fprintf, fprintf_unlocked): Add nonnull attribute.

> 	(printf, printf_unlocked, sprintf. vfprintf, vsprintf): Same.

> 	(__sprintf_chk, __vsprintf_chk, __fprintf_chk, __vfprintf_chk): Same.

> 	* calls.c (get_nonnull_ags, maybe_warn_null_arg): New functions.

> 	(initialize_argument_information): Diagnose null pointers passed to

> 	arguments declared nonnull.

> 	* calls.h (get_nonnull_args): Declared.

>

> gcc/c-family/ChangeLog:

>

> 	PR c/78673

> 	PR c/17308

> 	* c-common.c (check_nonnull_arg): Disable when optimization

> 	is enabled.

>

> gcc/testsuite/ChangeLog:

>

> 	PR c/78673

> 	PR c/17308

> 	* gcc.dg/builtins-nonnull.c: New test.

> 	* gcc.dg/nonnull-4.c: New test.

OK.
jeff
Jakub Jelinek Dec. 14, 2016, 10:15 p.m. UTC | #2
On Wed, Dec 07, 2016 at 08:24:33PM -0700, Martin Sebor wrote:
> @@ -8622,15 +8635,17 @@ rewrite_call_expr (location_t loc, tree exp, int skip, tree fndecl, int n, ...)

>  }

>  

>  /* Validate a single argument ARG against a tree code CODE representing

> -   a type.  */

> +   a type.  When NONNULL is true consider a pointer argument valid only

> +   if it's non-null.  Return true when argument is valid.  */

>  

>  static bool

> -validate_arg (const_tree arg, enum tree_code code)

> +validate_arg (const_tree arg, enum tree_code code, bool nonnull /*= false*/)

>  {

>    if (!arg)

>      return false;

>    else if (code == POINTER_TYPE)

> -    return POINTER_TYPE_P (TREE_TYPE (arg));

> +    return POINTER_TYPE_P (TREE_TYPE (arg))

> +      && (!nonnull || !integer_zerop (arg));

>    else if (code == INTEGER_TYPE)

>      return INTEGRAL_TYPE_P (TREE_TYPE (arg));

>    return code == TREE_CODE (TREE_TYPE (arg));


This is badly formatted, it would need to be
    return (POINTER_TYPE_P (TREE_TYPE (arg))
	    && (!nonnull || !integer_zerop (arg)));
but more importantly, we have 45 validate_arg calls that really don't care
about the nonnull stuff and a single one that cares.  I think
it would be better to revert the validate_arg changes and do:
	case POINTER_TYPE:
	  /* The actual argument must be nonnull when either the whole
	     called function has been declared nonnull, or when the formal
	     argument corresponding to the actual argument has been.  */
	  if (argmap)
	    nonnull = bitmap_empty_p (argmap) || bitmap_bit_p (argmap, argno);
	  arg = next_const_call_expr_arg (&iter);
	  if (!validate_arg (arg, code) || (nonnull && !integer_zerop (arg)))
	    goto end;
	  break;
	default:
	  /* If no parameters remain or the parameter's code does not
	     match the specified code, return false.  Otherwise continue
	     checking any remaining arguments.  */
	  arg = next_const_call_expr_arg (&iter);
	  if (!validate_arg (arg, code, nonnull))
	    goto end;
	  break;

	Jakub
diff mbox

Patch

PR c/78673 - sprintf missing attribute nonnull on destination argument
PR c/17308 - nonnull attribute not as useful as it could be

gcc/ChangeLog:

	PR c/78673
	PR c/17308
	* builtin-attrs.def (ATTR_NONNULL_1_1, ATTR_NONNULL_1_2): Defined.
	(ATTR_NONNULL_1_3, ATTR_NONNULL_1_4, ATTR_NONNULL_1_5): Same.
	(ATTR_NOTHROW_NONNULL_1_1, ATTR_NOTHROW_NONNULL_1_2): Same.
	(ATTR_NOTHROW_NONNULL_1_3, ATTR_NOTHROW_NONNULL_1_4): Same.
	(ATTR_NOTHROW_NONNULL_1_5): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_1_2): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_2_0): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_2_3): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_3_0): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_3_4): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_4_0): Same.
	(ATTR_NONNULL_1_FORMAT_PRINTF_4_5): Same.
	* builtins.c (validate_arg): Add argument.  Treat null pointers
	passed to nonnull arguments as invalid.
	(validate_arglist): Same.
	* builtins.def (fprintf, fprintf_unlocked): Add nonnull attribute.
	(printf, printf_unlocked, sprintf. vfprintf, vsprintf): Same.
	(__sprintf_chk, __vsprintf_chk, __fprintf_chk, __vfprintf_chk): Same.
	* calls.c (get_nonnull_ags, maybe_warn_null_arg): New functions.
	(initialize_argument_information): Diagnose null pointers passed to
	arguments declared nonnull.
	* calls.h (get_nonnull_args): Declared.

gcc/c-family/ChangeLog:

	PR c/78673
	PR c/17308
	* c-common.c (check_nonnull_arg): Disable when optimization
	is enabled.

gcc/testsuite/ChangeLog:

	PR c/78673
	PR c/17308
	* gcc.dg/builtins-nonnull.c: New test.
	* gcc.dg/nonnull-4.c: New test.

diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index 1520d15..c55523e 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -72,6 +72,9 @@  DEF_ATTR_FOR_STRING (STR1, "1")
 		      ATTR_##VALUE1, ATTR_LIST_##VALUE2)
 DEF_LIST_INT_INT (1,0)
 DEF_LIST_INT_INT (1,2)
+DEF_LIST_INT_INT (1,3)
+DEF_LIST_INT_INT (1,4)
+DEF_LIST_INT_INT (1,5)
 DEF_LIST_INT_INT (2,0)
 DEF_LIST_INT_INT (2,3)
 DEF_LIST_INT_INT (3,0)
@@ -205,6 +208,40 @@  DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_4, ATTR_NONNULL, ATTR_LIST_4, \
 /* Nothrow functions whose fifth parameter is a nonnull pointer.  */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_5, ATTR_NONNULL, ATTR_LIST_5, \
 			ATTR_NOTHROW_LIST)
+
+/* Same as ATTR_NONNULL_1.  */
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_1, ATTR_NONNULL, ATTR_LIST_1, ATTR_NULL)
+/* Functions like {v,}fprintf whose first and second parameters are
+   nonnull pointers.  As cancellation points the functions are not
+   nothrow.  */
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_2, ATTR_NONNULL, ATTR_LIST_1_2, ATTR_NULL)
+/* The following don't have {v,}fprintf forms.  They exist only to
+   make it possible to declare {v,}{f,s}printf attributes using
+   the same macro.  */
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_3, ATTR_NONNULL, ATTR_LIST_1_3, ATTR_NULL)
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_4, ATTR_NONNULL, ATTR_LIST_1_4, ATTR_NULL)
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_5, ATTR_NONNULL, ATTR_LIST_1_5, ATTR_NULL)
+
+/* Same as ATTR_NOTHROW_NONNULL_1.  */
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_1, ATTR_NONNULL, ATTR_LIST_1,
+		    ATTR_NOTHROW_LIST)
+/* Nothrow functions like {v,}sprintf whose first and second parameters
+   are nonnull pointers.  */
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_2, ATTR_NONNULL, ATTR_LIST_1_2, \
+		    ATTR_NOTHROW_LIST)
+/* Nothrow functions like {v,}snprintf whose first and third parameters
+   are nonnull pointers.  */
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_3, ATTR_NONNULL, ATTR_LIST_1_3, \
+		    ATTR_NOTHROW_LIST)
+/* Nothrow functions like {v,}sprintf_chk whose first and fourth parameters
+   are nonnull pointers.  */
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_4, ATTR_NONNULL, ATTR_LIST_1_4, \
+		    ATTR_NOTHROW_LIST)
+/* Nothrow functions like {v,}snprintf_chk whose first and fifth parameters
+   are nonnull pointers.  */
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_5, ATTR_NONNULL, ATTR_LIST_1_5, \
+		    ATTR_NOTHROW_LIST)
+			
 /* Nothrow leaf functions which are type-generic.  */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_TYPEGENERIC_LEAF, ATTR_TYPEGENERIC, ATTR_NULL, \
 			ATTR_NOTHROW_LEAF_LIST)
@@ -245,17 +282,23 @@  DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL, ATTR_MALLOC, ATTR_NULL, \
 DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \
 			ATTR_NOTHROW_NONNULL_LEAF)
 
-/* Construct a tree for a format attribute.  */
+/* Construct a tree for the format attribute (and implicitly nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE(TYPE, FA, VALUES)				 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
 		      ATTR_##TYPE, ATTR_LIST_##VALUES)			 \
   DEF_ATTR_TREE_LIST (ATTR_FORMAT_##TYPE##_##VALUES, ATTR_FORMAT,	 \
 		      ATTR_##TYPE##_##VALUES, ATTR_NONNULL_##FA)
+
+/* Construct a tree for the format and nothrow attributes (format
+   implies nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE_NOTHROW(TYPE, FA, VALUES)			 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
 		      ATTR_##TYPE, ATTR_LIST_##VALUES)			 \
   DEF_ATTR_TREE_LIST (ATTR_FORMAT_##TYPE##_NOTHROW_##VALUES, ATTR_FORMAT,\
 		      ATTR_##TYPE##_##VALUES, ATTR_NOTHROW_NONNULL_##FA)
+
+/* Construct one tree for the format attribute and another for the format
+   and nothrow attributes (in both cases format implies nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE_BOTH(TYPE, FA, VALUES)			 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
 		      ATTR_##TYPE, ATTR_LIST_##VALUES)			 \
@@ -263,6 +306,18 @@  DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \
 		      ATTR_##TYPE##_##VALUES, ATTR_NONNULL_##FA)	 \
   DEF_ATTR_TREE_LIST (ATTR_FORMAT_##TYPE##_NOTHROW_##VALUES, ATTR_FORMAT,\
 		      ATTR_##TYPE##_##VALUES, ATTR_NOTHROW_NONNULL_##FA)
+
+/* Construct a pair of trees for the nonnull attribute for the first
+   argument, plus format printf attribute (format implies nonnull):
+   the first ordinary and the second nothrow.  */
+#define DEF_FORMAT_ATTRIBUTE_NONNULL(TYPE, FA, VALUES)			 \
+  DEF_ATTR_TREE_LIST (ATTR_NONNULL_1_FORMAT_##TYPE##_##VALUES,   	 \
+  		      ATTR_FORMAT, ATTR_##TYPE##_##VALUES, 	 	 \
+		      ATTR_NONNULL_1_##FA)				 \
+  DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1_FORMAT_##TYPE##_##VALUES,   \
+  		      ATTR_FORMAT, ATTR_##TYPE##_##VALUES, 	 	 \
+		      ATTR_NOTHROW_NONNULL_1_##FA)
+
 DEF_FORMAT_ATTRIBUTE(PRINTF,1,1_0)
 DEF_FORMAT_ATTRIBUTE(PRINTF,1,1_2)
 DEF_FORMAT_ATTRIBUTE_BOTH(PRINTF,2,2_0)
@@ -273,6 +328,26 @@  DEF_FORMAT_ATTRIBUTE_NOTHROW(PRINTF,4,4_0)
 DEF_FORMAT_ATTRIBUTE_NOTHROW(PRINTF,4,4_5)
 DEF_FORMAT_ATTRIBUTE_NOTHROW(PRINTF,5,5_0)
 DEF_FORMAT_ATTRIBUTE_NOTHROW(PRINTF,5,5_6)
+
+/* Attributes for fprintf(f, f, va).  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,1,1_2)
+/* Attributes for v{f,s}printf(d, f, va).  vsprintf is nothrow, vfprintf
+   is not.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,2,2_0)
+/* Attributes for {f,s}printf(d, f, ...).  sprintf is nothrow, fprintf
+   is not.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,2,2_3)
+/* Attributes for vprintf_chk.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,3,3_0)
+/* Attributes for printf_chk.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,3,3_4)
+/* Attributes for v{f,s}printf_chk(d, t, bos, f, va).  vsprintf_chk is
+   nothrow, vfprintf_chk is not.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,4,4_0)
+/* Attributes for {f,s}printf_chk(d, t, bos, f, ...).  sprintf_chk is
+   nothrow, fprintf_chk is not.  */
+DEF_FORMAT_ATTRIBUTE_NONNULL(PRINTF,4,4_5)
+
 DEF_FORMAT_ATTRIBUTE(SCANF,1,1_0)
 DEF_FORMAT_ATTRIBUTE(SCANF,1,1_2)
 DEF_FORMAT_ATTRIBUTE_BOTH(SCANF,2,2_0)
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 1316c27..9777081 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -145,7 +145,7 @@  static tree fold_builtin_classify_type (tree);
 static tree fold_builtin_strlen (location_t, tree, tree);
 static tree fold_builtin_inf (location_t, tree, int);
 static tree rewrite_call_expr (location_t, tree, int, tree, int, ...);
-static bool validate_arg (const_tree, enum tree_code code);
+static bool validate_arg (const_tree, enum tree_code code, bool = false);
 static rtx expand_builtin_fabs (tree, rtx, rtx);
 static rtx expand_builtin_signbit (tree, rtx);
 static tree fold_builtin_memcmp (location_t, tree, tree, tree);
@@ -1033,7 +1033,7 @@  more_const_call_expr_args_p (const const_call_expr_arg_iterator *iter)
 
 /* This function validates the types of a function call argument list
    against a specified list of tree_codes.  If the last specifier is a 0,
-   that represents an ellipses, otherwise the last specifier must be a
+   that represents an ellipsis, otherwise the last specifier must be a
    VOID_TYPE.  */
 
 static bool
@@ -1048,9 +1048,14 @@  validate_arglist (const_tree callexpr, ...)
   va_start (ap, callexpr);
   init_const_call_expr_arg_iterator (callexpr, &iter);
 
-  do
+  /* Get a bitmap of pointer argument numbers declared attribute nonnull.  */
+  bitmap argmap = get_nonnull_args (callexpr);
+
+  for (unsigned argno = 1; ; ++argno)
     {
       code = (enum tree_code) va_arg (ap, int);
+      bool nonnull = false;
+
       switch (code)
 	{
 	case 0:
@@ -1062,23 +1067,31 @@  validate_arglist (const_tree callexpr, ...)
 	     true, otherwise return false.  */
 	  res = !more_const_call_expr_args_p (&iter);
 	  goto end;
+	case POINTER_TYPE:
+	  /* The actual argument must be nonnull when either the whole
+	     called function has been declared nonnull, or when the formal
+	     argument corresponding to the actual argument has been.  */
+	  if (argmap)
+	    nonnull = bitmap_empty_p (argmap) || bitmap_bit_p (argmap, argno);
+	  /* FALLTHRU */
 	default:
 	  /* If no parameters remain or the parameter's code does not
 	     match the specified code, return false.  Otherwise continue
 	     checking any remaining arguments.  */
 	  arg = next_const_call_expr_arg (&iter);
-	  if (!validate_arg (arg, code))
+	  if (!validate_arg (arg, code, nonnull))
 	    goto end;
 	  break;
 	}
     }
-  while (1);
 
   /* We need gotos here since we can only have one VA_CLOSE in a
      function.  */
  end: ;
   va_end (ap);
 
+  BITMAP_FREE (argmap);
+
   return res;
 }
 
@@ -8622,15 +8635,17 @@  rewrite_call_expr (location_t loc, tree exp, int skip, tree fndecl, int n, ...)
 }
 
 /* Validate a single argument ARG against a tree code CODE representing
-   a type.  */
+   a type.  When NONNULL is true consider a pointer argument valid only
+   if it's non-null.  Return true when argument is valid.  */
 
 static bool
-validate_arg (const_tree arg, enum tree_code code)
+validate_arg (const_tree arg, enum tree_code code, bool nonnull /*= false*/)
 {
   if (!arg)
     return false;
   else if (code == POINTER_TYPE)
-    return POINTER_TYPE_P (TREE_TYPE (arg));
+    return POINTER_TYPE_P (TREE_TYPE (arg))
+      && (!nonnull || !integer_zerop (arg));
   else if (code == INTEGER_TYPE)
     return INTEGRAL_TYPE_P (TREE_TYPE (arg));
   return code == TREE_CODE (TREE_TYPE (arg));
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 9cd24e8..24b34e8 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -683,8 +683,8 @@  DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST
 DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
 
 /* Category: stdio builtins.  */
-DEF_LIB_BUILTIN        (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_2_3)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPRINTF_UNLOCKED, "fprintf_unlocked", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_2_3)
+DEF_LIB_BUILTIN        (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPRINTF_UNLOCKED, "fprintf_unlocked", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3)
 DEF_LIB_BUILTIN        (BUILT_IN_PUTC, "putc", BT_FN_INT_INT_FILEPTR, ATTR_NONNULL_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_PUTC_UNLOCKED, "putc_unlocked", BT_FN_INT_INT_FILEPTR, ATTR_NONNULL_LIST)
 DEF_LIB_BUILTIN        (BUILT_IN_FPUTC, "fputc", BT_FN_INT_INT_FILEPTR, ATTR_NONNULL_LIST)
@@ -695,21 +695,22 @@  DEF_LIB_BUILTIN        (BUILT_IN_FSCANF, "fscanf", BT_FN_INT_FILEPTR_CONST_STRIN
 DEF_LIB_BUILTIN        (BUILT_IN_FWRITE, "fwrite", BT_FN_SIZE_CONST_PTR_SIZE_SIZE_FILEPTR, ATTR_NONNULL_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_FWRITE_UNLOCKED, "fwrite_unlocked", BT_FN_SIZE_CONST_PTR_SIZE_SIZE_FILEPTR, ATTR_NONNULL_LIST)
 DEF_LIB_BUILTIN        (BUILT_IN_PRINTF, "printf", BT_FN_INT_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_1_2)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_PRINTF_UNLOCKED, "printf_unlocked", BT_FN_INT_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_1_2)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_PRINTF_UNLOCKED, "printf_unlocked", BT_FN_INT_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_1_2)
 DEF_LIB_BUILTIN        (BUILT_IN_PUTCHAR, "putchar", BT_FN_INT_INT, ATTR_NULL)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_PUTCHAR_UNLOCKED, "putchar_unlocked", BT_FN_INT_INT, ATTR_NULL)
 DEF_LIB_BUILTIN        (BUILT_IN_PUTS, "puts", BT_FN_INT_CONST_STRING, ATTR_NONNULL_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_PUTS_UNLOCKED, "puts_unlocked", BT_FN_INT_CONST_STRING, ATTR_NONNULL_LIST)
 DEF_LIB_BUILTIN        (BUILT_IN_SCANF, "scanf", BT_FN_INT_CONST_STRING_VAR, ATTR_FORMAT_SCANF_1_2)
 DEF_C99_BUILTIN        (BUILT_IN_SNPRINTF, "snprintf", BT_FN_INT_STRING_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_3_4)
-DEF_LIB_BUILTIN        (BUILT_IN_SPRINTF, "sprintf", BT_FN_INT_STRING_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_2_3)
+
+DEF_LIB_BUILTIN        (BUILT_IN_SPRINTF, "sprintf", BT_FN_INT_STRING_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_2_3)
 DEF_LIB_BUILTIN        (BUILT_IN_SSCANF, "sscanf", BT_FN_INT_CONST_STRING_CONST_STRING_VAR, ATTR_FORMAT_SCANF_NOTHROW_2_3)
-DEF_LIB_BUILTIN        (BUILT_IN_VFPRINTF, "vfprintf", BT_FN_INT_FILEPTR_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_2_0)
+DEF_LIB_BUILTIN        (BUILT_IN_VFPRINTF, "vfprintf", BT_FN_INT_FILEPTR_CONST_STRING_VALIST_ARG, ATTR_NONNULL_1_FORMAT_PRINTF_2_0)
 DEF_C99_BUILTIN        (BUILT_IN_VFSCANF, "vfscanf", BT_FN_INT_FILEPTR_CONST_STRING_VALIST_ARG, ATTR_FORMAT_SCANF_2_0)
 DEF_LIB_BUILTIN        (BUILT_IN_VPRINTF, "vprintf", BT_FN_INT_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_1_0)
 DEF_C99_BUILTIN        (BUILT_IN_VSCANF, "vscanf", BT_FN_INT_CONST_STRING_VALIST_ARG, ATTR_FORMAT_SCANF_1_0)
 DEF_C99_BUILTIN        (BUILT_IN_VSNPRINTF, "vsnprintf", BT_FN_INT_STRING_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_3_0)
-DEF_LIB_BUILTIN        (BUILT_IN_VSPRINTF, "vsprintf", BT_FN_INT_STRING_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_2_0)
+DEF_LIB_BUILTIN        (BUILT_IN_VSPRINTF, "vsprintf", BT_FN_INT_STRING_CONST_STRING_VALIST_ARG, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_2_0)
 DEF_C99_BUILTIN        (BUILT_IN_VSSCANF, "vsscanf", BT_FN_INT_CONST_STRING_CONST_STRING_VALIST_ARG, ATTR_FORMAT_SCANF_NOTHROW_2_0)
 
 /* Category: ctype builtins.  */
@@ -926,12 +927,12 @@  DEF_EXT_LIB_BUILTIN_CHKP (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRI
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCAT_CHK, "__strncat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SNPRINTF_CHK, "__snprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_5_6)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_4_5)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_5)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSNPRINTF_CHK, "__vsnprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_5_0)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSPRINTF_CHK, "__vsprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_4_0)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPRINTF_CHK, "__fprintf_chk", BT_FN_INT_FILEPTR_INT_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_3_4)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSPRINTF_CHK, "__vsprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_0)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPRINTF_CHK, "__fprintf_chk", BT_FN_INT_FILEPTR_INT_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_3_4)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_PRINTF_CHK, "__printf_chk", BT_FN_INT_INT_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_2_3)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_VFPRINTF_CHK, "__vfprintf_chk", BT_FN_INT_FILEPTR_INT_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_3_0)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_VFPRINTF_CHK, "__vfprintf_chk", BT_FN_INT_FILEPTR_INT_CONST_STRING_VALIST_ARG, ATTR_NONNULL_1_FORMAT_PRINTF_3_0)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_VPRINTF_CHK, "__vprintf_chk", BT_FN_INT_INT_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_2_0)
 
 /* Profiling hooks.  */
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 0749361..ac624d8 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5388,7 +5388,10 @@  check_nonnull_arg (void *ctx, tree param, unsigned HOST_WIDE_INT param_num)
   if (TREE_CODE (TREE_TYPE (param)) != POINTER_TYPE)
     return;
 
-  if (integer_zerop (param))
+  /* When not optimizing diagnose the simple cases of null arguments.
+     When optimization is enabled defer the checking until expansion
+     when more cases can be detected.  */
+  if (!optimize && integer_zerop (param))
     warning_at (*ploc, OPT_Wnonnull, "null argument where non-null required "
 		"(argument %lu)", (unsigned long) param_num);
 }
diff --git a/gcc/calls.c b/gcc/calls.c
index 21385ce..284e798 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -1194,6 +1194,91 @@  maybe_complain_about_tail_call (tree call_expr, const char *reason)
   error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason);
 }
 
+/* Return a bitmap with a bit set corresponding to each argument in
+   a function call expression CALLEXPR declared with attribute nonnull,
+   or null if none of the function's argument are nonnull.  The caller
+   must free the bitmap.  */
+
+bitmap
+get_nonnull_args (const_tree callexpr)
+{
+  tree fn = CALL_EXPR_FN (callexpr);
+  if (!fn || TREE_CODE (fn) != ADDR_EXPR)
+    return NULL;
+
+  tree fndecl = TREE_OPERAND (fn, 0);
+  tree fntype = TREE_TYPE (fndecl);
+  tree attrs = TYPE_ATTRIBUTES (fntype);
+  if (!attrs)
+    return NULL;
+
+  bitmap argmap = NULL;
+
+  /* A function declaration can specify multiple attribute nonnull,
+     each with zero or more arguments.  The loop below creates a bitmap
+     representing a union of all the arguments.  An empty (but non-null)
+     bitmap means that all arguments have been declaraed nonnull.  */
+  for ( ; attrs; attrs = TREE_CHAIN (attrs))
+    {
+      attrs = lookup_attribute ("nonnull", attrs);
+      if (!attrs)
+	break;
+
+      if (!argmap)
+	argmap = BITMAP_ALLOC (NULL);
+
+      if (!TREE_VALUE (attrs))
+	{
+	  /* Clear the bitmap in case a previous attribute nonnull
+	     set it and this one overrides it for all arguments.  */
+	  bitmap_clear (argmap);
+	  return argmap;
+	}
+
+      /* Iterate over the indices of the format arguments declared nonnull
+	 and set a bit for each.  */
+      for (tree idx = TREE_VALUE (attrs); idx; idx = TREE_CHAIN (idx))
+	{
+	  unsigned int val = TREE_INT_CST_LOW (TREE_VALUE (idx)) - 1;
+	  bitmap_set_bit (argmap, val);
+	}
+    }
+
+  return argmap;
+}
+
+/* In a call EXP to a function FNDECL some of whose arguments may have
+   been declared with attribute nonnull as described by NONNULLARGS,
+   check actual argument ARG at the zero-based position ARGPOS for
+   equality to null and issue a warning if it is not expected to be.  */
+
+static void
+maybe_warn_null_arg (tree fndecl, tree exp, tree arg,
+		     unsigned argpos, bitmap nonnullargs)
+{
+  if (!optimize
+      || !nonnullargs
+      || TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
+      || !integer_zerop (arg)
+      || (!bitmap_empty_p (nonnullargs)
+	  && !bitmap_bit_p (nonnullargs, argpos)))
+    return;
+
+  ++argpos;
+
+  location_t exploc EXPR_LOCATION (exp);
+
+  if (warning_at (exploc, OPT_Wnonnull,
+		  "argument %u null where non-null expected", argpos))
+    {
+      if (DECL_IS_BUILTIN (fndecl))
+	inform (exploc, "in a call to built-in function %qD", fndecl);
+      else
+	inform (DECL_SOURCE_LOCATION (fndecl),
+		"in a call to function %qD declared here", fndecl);
+    }
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1359,6 +1444,9 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   bitmap_obstack_release (NULL);
 
+  /* Get a bitmap of pointer argument numbers declared attribute nonnull.  */
+  bitmap nonnullargs = get_nonnull_args (exp);
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -1590,12 +1678,18 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       if (args[i].locate.size.var)
 	ADD_PARM_SIZE (*args_size, args[i].locate.size.var);
 
+      /* Check pointer argument for equality to NULL that is being passed
+	 to arguments declared with attribute nonnull and warn. */
+      maybe_warn_null_arg (fndecl, exp, args[i].tree_value, argpos, nonnullargs);
+
       /* Increment ARGS_SO_FAR, which has info about which arg-registers
 	 have been used, etc.  */
 
       targetm.calls.function_arg_advance (args_so_far, TYPE_MODE (type),
 					  type, argpos < n_named_args);
     }
+
+  BITMAP_FREE (nonnullargs);
 }
 
 /* Update ARGS_SIZE to contain the total size for the argument block.
diff --git a/gcc/calls.h b/gcc/calls.h
index e144156..5edc1db 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -37,7 +37,7 @@  extern bool pass_by_reference (CUMULATIVE_ARGS *, machine_mode,
 			       tree, bool);
 extern bool reference_callee_copied (CUMULATIVE_ARGS *, machine_mode,
 				     tree, bool);
-
+extern bitmap get_nonnull_args (const_tree);
 
 
 #endif // GCC_CALLS_H
diff --git a/gcc/testsuite/gcc.dg/builtins-nonnull.c b/gcc/testsuite/gcc.dg/builtins-nonnull.c
new file mode 100644
index 0000000..fa9eaf2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtins-nonnull.c
@@ -0,0 +1,239 @@ 
+/* PR c/17308 - nonnull attribute not as useful as it could be
+   PR c/78673 - sprintf missing attribute nonnull on destination argument
+   { dg-do "compile" }
+   { dg-additional-options "-O2 -Wnonnull -ftrack-macro-expansion=0 -std=c99" } */
+
+#define va_list __builtin_va_list
+
+typedef struct FILE FILE;
+
+char* null (void)
+{
+  return 0;
+}
+
+void sink (int, ...);
+#define T(arg) sink (0, arg)
+
+
+#define bzero    __builtin_bzero
+#define memcpy   __builtin_memcpy
+#define memmove  __builtin_memmove
+#define mempcpy  __builtin_mempcpy
+#define memset   __builtin_memset
+
+void test_memfuncs (void *s, unsigned n)
+{
+  /* Bzero is not declared attribute nonnull.  */
+  bzero (null (), n);
+
+  T (memcpy (null (), s, n));     /* { dg-warning "argument 1 null where non-null expected" } */
+  T (memcpy (s, null (), n));     /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (memmove (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (memmove (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (mempcpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (mempcpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (memset (null (), 0, n));     /* { dg-warning "argument 1 null where non-null expected" } */
+}
+
+#undef memcpy
+#undef memmove
+#undef mempcpy
+#undef memset
+#define memcpy(d, s, n)   __builtin___memcpy_chk (d, s, n, n)
+#define memmove(d, s, n)  __builtin___memmove_chk (d, s, n, n)
+#define mempcpy(d, s, n)  __builtin___mempcpy_chk (d, s, n, n)
+#define memset(d, x, n)   __builtin___memset_chk (d, x, n, n)
+
+void test_memfuncs_chk (void *s, unsigned n)
+{
+  T (memcpy (null (), s, n));     /* { dg-warning "argument 1 null where non-null expected" } */
+  T (memcpy (s, null (), n));     /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (memmove (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (memmove (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (mempcpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (mempcpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (memset (null (), 0, n));     /* { dg-warning "argument 1 null where non-null expected" } */
+}
+
+
+#define strcat   __builtin_strcat
+#define strchr   __builtin_strchr
+#define stpcpy   __builtin_stpcpy
+#define stpncpy  __builtin_stpncpy
+#define strcpy   __builtin_strcpy
+#define strncpy  __builtin_strncpy
+#define strlen   __builtin_strlen
+#define strncat  __builtin_strncat
+#define strstr   __builtin_strstr
+
+void test_strfuncs (char *s, unsigned n)
+{
+  T (strcat (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strcat (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strchr (null (), 'x'));      /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (stpcpy (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (stpcpy (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (stpncpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (stpncpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strcpy (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strcpy (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strncpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strncpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strlen (null ()));           /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (strncat (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+  T (strncat (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (strstr (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strstr (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+}
+
+
+#undef strcat
+#undef stpcpy
+#undef stpncpy
+#undef strcpy
+#undef strncpy
+#undef strncat
+
+#define strcat(d, s)      __builtin___strcat_chk (d, s, n)
+#define stpcpy(d, s)      __builtin___stpcpy_chk (d, s, n)
+#define stpncpy(d, s, n)  __builtin___stpncpy_chk (d, s, n, n)
+#define strcpy(d, s)      __builtin___strcpy_chk (d, s, n)
+#define strncpy(d, s, n)  __builtin___strncpy_chk (d, s, n, n)
+#define strncat(d, s, n)  __builtin___strncat_chk (d, s, n, n)
+
+void test_strfuncs_chk (char *s, unsigned n)
+{
+  T (strcat (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strcat (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strchr (null (), 'x'));      /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (stpcpy (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (stpcpy (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (stpncpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (stpncpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strcpy (null (), s));        /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strcpy (s, null ()));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strncpy (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+  T (strncpy (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (strncat (s, null (), n));    /* { dg-warning "argument 2 null where non-null expected" } */
+  T (strncat (null (), s, n));    /* { dg-warning "argument 1 null where non-null expected" } */
+}
+
+
+#define fprintf             __builtin_fprintf
+#define fprintf_unlocked    __builtin_fprintf_unlocked
+#define vfprintf            __builtin_vfprintf
+#define printf              __builtin_printf
+#define printf_unlocked     __builtin_printf_unlocked
+#define vprintf             __builtin_vprintf
+#define sprintf             __builtin_sprintf
+#define snprintf            __builtin_snprintf
+#define vsprintf            __builtin_vsprintf
+#define vsnprintf           __builtin_vsnprintf
+
+void test_stdio_funcs (FILE *f, char *d, unsigned n, va_list va)
+{
+  T (fprintf (null (), "%i", 0)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (fprintf (f, null ()));       /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (fprintf_unlocked (null (), "%i", 0)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (fprintf_unlocked (f, null ()));       /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (vfprintf (null (), "%i", va));/* { dg-warning "argument 1 null where non-null expected" } */
+  T (vfprintf (f, null (), va));   /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (vprintf (null (), va));      /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (printf (null ()));           /* { dg-warning "argument 1 null where non-null expected" } */
+  T (printf_unlocked (null ()));  /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (vprintf (null (), va));      /* { dg-warning "argument 1 null where non-null expected" } */
+
+  T (sprintf (null (), "%i", 0)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (sprintf (d, null ()));       /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (snprintf (null (), n, "%i", 0));
+  T (snprintf (d, n, null ()));   /* { dg-warning "argument 3 null where non-null expected" } */
+
+  T (vsprintf (null (), "%i", va)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (vsprintf (d, null (), va));   /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (vsnprintf (null (), n, "%i", va));
+  T (vsnprintf (d, n, null (), va));  /* { dg-warning "argument 3 null where non-null expected" } */
+}
+
+#undef fprintf
+#undef fprintf_unlocked
+#undef vfprintf
+#undef printf
+#undef printf_unlocked
+#undef vprintf
+#undef sprintf
+#undef snprintf
+#undef vsprintf
+#undef vsnprintf
+
+#define fprintf(f, fmt, ...)				\
+  __builtin___fprintf_chk (f, 0, fmt, __VA_ARGS__)
+#define vfprintf(f, fmt, va)			\
+  __builtin___vfprintf_chk (f, 0, fmt, va)
+#define printf(fmt, ...)			\
+  __builtin___printf_chk (0, fmt, __VA_ARGS__)
+#define vprintf(fmt, va)			\
+  __builtin___vprintf_chk (0, fmt, va)
+#define sprintf(d, fmt, ... )				\
+  __builtin___sprintf_chk (d, 0, n, fmt, __VA_ARGS__)
+#define snprintf(d, n, fmt, ...)			\
+  __builtin___snprintf_chk (d, n, 0, n,  fmt, __VA_ARGS__)
+#define vsprintf(d, fmt, va)			\
+  __builtin___vsprintf_chk (d, 0, n, fmt, va)
+#define vsnprintf(d, n, fmt, va)			\
+  __builtin___vsnprintf_chk (d, n, 0, n, fmt, va)
+
+void test_stdio_funcs_chk (FILE *f, char *d, const char *fmt,
+			   unsigned n, va_list va)
+{
+  T (fprintf (null (), "%i", 0)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (fprintf (f, null (), 0));    /* { dg-warning "argument 3 null where non-null expected" } */
+
+  T (vfprintf (null (), "%i", va));/* { dg-warning "argument 1 null where non-null expected" } */
+  T (vfprintf (f, null (), va));   /* { dg-warning "argument 3 null where non-null expected" } */
+
+  T (vprintf (null (), va));      /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (printf (null (), 0));        /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (vprintf (null (), va));      /* { dg-warning "argument 2 null where non-null expected" } */
+
+  T (sprintf (null (), "%i", 0)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (sprintf (d, null (), 0));    /* { dg-warning "argument 4 null where non-null expected" } */
+
+  T (snprintf (null (), n, "%i", 0));
+  T (snprintf (d, n, null (), 0));  /* { dg-warning "argument 5 null where non-null expected" } */
+
+  T (vsprintf (null (), "%i", va)); /* { dg-warning "argument 1 null where non-null expected" } */
+  T (vsprintf (d, null (), va));   /* { dg-warning "argument 4 null where non-null expected" } */
+
+  T (vsnprintf (null (), n, "%i", va));
+  T (vsnprintf (d, n, null (), va));  /* { dg-warning "argument 5 null where non-null expected" } */
+}
diff --git a/gcc/testsuite/gcc.dg/nonnull-4.c b/gcc/testsuite/gcc.dg/nonnull-4.c
new file mode 100644
index 0000000..577a04c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/nonnull-4.c
@@ -0,0 +1,79 @@ 
+/* PR c/78673 - sprintf missing attribute nonnull on destination argument
+   Test to verify that calls to user-defined functions declared with
+   the "nonnull" function attribute are diagnosed.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wnonnull" } */
+
+#define N(...) __attribute__ ((nonnull (__VA_ARGS__)))
+
+void N (1) f1_1 (void*);
+
+void N (1)       f2_1 (void*, void*);
+void N (1) N (2) f2_1_2 (void*, void*);
+
+void N (1) N (3) f3_1_3 (void*, void*, void*);
+
+void N (1, 2) N (4) g4_1_2_4 (void*, void*, void*, void*);
+void N (1, 3) N (4) g4_1_3_4 (void*, void*, void*, void*);
+void N (2, 3, 4)    g4_2_3_4 (void*, void*, void*, void*);
+
+void N () g4_all (void*, void*, void*, void*);
+
+void N (1, 3, 5, 7, 11, 13)
+g16_1_3_5_7_11_13 (void*, void*, void*, void*,
+		   void*, void*, void*, void*,
+		   void*, void*, void*, void*,
+		   void*, void*, void*, void*);
+
+void* null (void) { return 0; }
+
+void test (void)
+{
+  void *p0 = null ();
+  void *px = &px;
+
+  f1_1 (p0);   /* { dg-warning "argument 1 null where non-null expected " } */
+  f1_1 (px);
+
+  f2_1 (p0, px);  /* { dg-warning "argument 1 null" } */
+  f2_1 (px, p0);
+  f2_1 (p0, p0);  /* { dg-warning "argument 1 null" } */
+
+  f2_1_2 (p0, px);  /* { dg-warning "argument 1 null" } */
+  f2_1_2 (px, p0);  /* { dg-warning "argument 2 null" } */
+  f2_1_2 (p0, p0);  /* { dg-warning "argument 1 null" } */
+  /* { dg-warning "argument 2 null" "argument 2" { target *-*-* } .-1 } */
+
+  f3_1_3 (p0, px, px);  /* { dg-warning "argument 1 null" } */
+  f3_1_3 (px, p0, px);
+  f3_1_3 (px, px, p0);  /* { dg-warning "argument 3 null" } */
+  f3_1_3 (p0, p0, px);  /* { dg-warning "argument 1 null" } */
+  f3_1_3 (px, p0, p0);  /* { dg-warning "argument 3 null" } */
+  f3_1_3 (p0, p0, p0);  /* { dg-warning "argument 1 null" } */
+  /* { dg-warning "argument 3 null" "argument 3" { target *-*-* } .-1 } */
+
+  g4_1_2_4 (p0, px, px, px);  /* { dg-warning "argument 1 null" } */
+  g4_1_2_4 (px, p0, px, px);  /* { dg-warning "argument 2 null" } */
+  g4_1_2_4 (px, px, p0, px);
+  g4_1_2_4 (px, px, px, p0);  /* { dg-warning "argument 4 null" } */
+
+  g4_1_3_4 (p0, px, px, px);  /* { dg-warning "argument 1 null" } */
+  g4_1_3_4 (px, p0, px, px);
+  g4_1_3_4 (px, px, p0, px);  /* { dg-warning "argument 3 null" } */
+  g4_1_3_4 (px, px, px, p0);  /* { dg-warning "argument 4 null" } */
+
+  g4_2_3_4 (p0, px, px, px);
+  g4_2_3_4 (px, p0, px, px);  /* { dg-warning "argument 2 null" } */
+  g4_2_3_4 (px, px, p0, px);  /* { dg-warning "argument 3 null" } */
+  g4_2_3_4 (px, px, px, p0);  /* { dg-warning "argument 4 null" } */
+
+  g4_all (p0, px, px, px);  /* { dg-warning "argument 1 null" } */
+  g4_all (px, p0, px, px);  /* { dg-warning "argument 2 null" } */
+  g4_all (px, px, p0, px);  /* { dg-warning "argument 3 null" } */
+  g4_all (px, px, px, p0);  /* { dg-warning "argument 4 null" } */
+
+  g16_1_3_5_7_11_13 (px, px, px, px, px, px, px, px,
+		     px, px, px, px, px, px, px, px);
+
+  g16_1_3_5_7_11_13 (px, p0, px, p0, px, p0, px, p0, p0, p0, px, p0, p0, p0, p0, p0);   /* { dg-warning "argument 13 null" } */
+}