Hi!

As mentioned in an earlier thread, C2Y voted in a change which made
various library APIs callable with NULL arguments in certain cases,
e.g.
memcpy (NULL, NULL, 0);
is now valid, although
memcpy (NULL, NULL, 1);
remains invalid.  This affects various APIs, including several of
GCC builtins; plus on the C library side those APIs are often declared
with nonnull attribute(s) as well.

Florian suggested using the access attribute for this, but our docs
explicitly say that access attribute doesn't imply nonnull and it doesn't
cover e.g. the qsort case where the comparison function pointer may be
also NULL if nmemb is 0, but must be non-zero otherwise.
As this case affects 21 APIs in C standard and I think is going to affect
various wrappers around those in various packages as well, I think it
is a common thing that should have its own attribute, because we should
still warn when people use
qsort (NULL, 1, 1, NULL);
etc., and similarly want to have -fsanitize=null instrumentation for those.

So, the following patch introduces nonnull_if_nonzero attribute (or would
you prefer cond_nonnull or some other name?), which has always 2 arguments,
argument index of a pointer argument (like one argument nonnull) and
argument index of an associated integral argument.  If that argument is
non-zero, it is UB to pass NULL to the pointer argument, if that argument
is zero, it is valid.  And changes various spots which already handled the
nonnull attribute to handle this one as well, with sometimes using the
ranger (or for -fsanitize=nonnull explicitly checking the associated
argument value, so instead of if (!ptr) __ubsan_... (...); it will
now do if (!ptr && sz) __ubsan_... (...);).
I've so far omitted changing gimple_infer_range (am not 100% sure how I can
use the ranger inside of the ranger) and changing the analyzer to handle it.
And I haven't changed builtins.def etc. to make use of that attribute
instead of nonnull where appropriate.

What do you think about this?  So far lightly tested.
Ok for trunk if it passes full bootstrap/regtest?
I'd then follow with the builtins.def changes (and eventually glibc
etc. would need to be adjusted too).

2024-11-12  Jakub Jelinek  <ja...@redhat.com>

        PR c/117023
gcc/
        * gimple.h (infer_nonnull_range_by_attribute): Add a tree *
        argument defaulted to NULL.
        * gimple.cc (infer_nonnull_range_by_attribute): Add op2 argument.
        Handle also nonnull_if_nonzero attributes.
        * tree.cc (get_nonnull_args): Fix comment typo.
        * builtins.cc (validate_arglist): Handle nonnull_if_nonzero attribute.
        * tree-ssa-ccp.cc (pass_post_ipa_warn::execute): Handle
        nonnull_if_nonzero attributes.
        * ubsan.cc (instrument_nonnull_arg): Adjust
        infer_nonnull_range_by_attribute caller.  If it returned true and
        filed in non-NULL arg2, check that arg2 is non-zero as another
        condition next to checking that arg is zero.
        * doc/extend.texi (nonnull_if_nonzero): Document new attribute.
gcc/c-family/
        * c-attribs.cc (handle_nonnull_if_nonzero_attribute): New
        function.
        (c_common_gnu_attributes): Add nonnull_if_nonzero attribute.
        (handle_nonnull_attribute): Fix comment typo.
        * c-common.cc (struct nonnull_arg_ctx): Add other member.
        (check_function_nonnull): Also check nonnull_if_nonzero attributes.
        (check_nonnull_arg): Use different warning wording if pctx->other
        is non-zero.
        (check_function_arguments): Initialize ctx.other.
gcc/testsuite/
        * gcc.dg/nonnull-8.c: New test.
        * gcc.dg/nonnull-9.c: New test.
        * gcc.dg/nonnull-10.c: New test.
        * c-c++-common/ubsan/nonnull-6.c: New test.
        * c-c++-common/ubsan/nonnull-7.c: New test.

--- gcc/gimple.h.jj     2024-09-23 16:01:12.393215457 +0200
+++ gcc/gimple.h        2024-11-12 12:24:06.544215672 +0100
@@ -1661,7 +1661,7 @@ extern bool nonfreeing_call_p (gimple *)
 extern bool nonbarrier_call_p (gimple *);
 extern bool infer_nonnull_range (gimple *, tree);
 extern bool infer_nonnull_range_by_dereference (gimple *, tree);
-extern bool infer_nonnull_range_by_attribute (gimple *, tree);
+extern bool infer_nonnull_range_by_attribute (gimple *, tree, tree * = NULL);
 extern void sort_case_labels (vec<tree> &);
 extern void preprocess_case_label_vec_for_gimple (vec<tree> &, tree, tree *);
 extern void gimple_seq_set_location (gimple_seq, location_t);
--- gcc/gimple.cc.jj    2024-10-31 08:45:38.241824084 +0100
+++ gcc/gimple.cc       2024-11-12 14:30:29.104618853 +0100
@@ -3089,10 +3089,16 @@ infer_nonnull_range_by_dereference (gimp
 }
 
 /* Return true if OP can be inferred to be a non-NULL after STMT
-   executes by using attributes.  */
+   executes by using attributes.  If OP2 is non-NULL and nonnull_if_nonzero
+   is the only attribute implying OP being non-NULL and the corresponding
+   argument isn't non-zero INTEGER_CST, set *OP2 to the corresponding
+   argument.  */
 bool
-infer_nonnull_range_by_attribute (gimple *stmt, tree op)
+infer_nonnull_range_by_attribute (gimple *stmt, tree op, tree *op2)
 {
+  if (op2)
+    *op2 = NULL_TREE;
+
   /* We can only assume that a pointer dereference will yield
      non-NULL if -fdelete-null-pointer-checks is enabled.  */
   if (!flag_delete_null_pointer_checks
@@ -3109,9 +3115,10 @@ infer_nonnull_range_by_attribute (gimple
          attrs = lookup_attribute ("nonnull", attrs);
 
          /* If "nonnull" wasn't specified, we know nothing about
-            the argument.  */
+            the argument, unless "nonnull_if_nonzero" attribute is
+            present.  */
          if (attrs == NULL_TREE)
-           return false;
+           break;
 
          /* If "nonnull" applies to all the arguments, then ARG
             is non-null if it's in the argument list.  */
@@ -3138,6 +3145,37 @@ infer_nonnull_range_by_attribute (gimple
                }
            }
        }
+
+      for (attrs = TYPE_ATTRIBUTES (fntype);
+          (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+          attrs = TREE_CHAIN (attrs))
+       {
+         tree args = TREE_VALUE (attrs);
+         unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+         unsigned int idx2
+           = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+         if (idx < gimple_call_num_args (stmt)
+             && idx2 < gimple_call_num_args (stmt)
+             && operand_equal_p (op, gimple_call_arg (stmt, idx), 0))
+           {
+             tree arg2 = gimple_call_arg (stmt, idx2);
+             if (!INTEGRAL_TYPE_P (TREE_TYPE (arg2)))
+               return false;
+             if (integer_nonzerop (arg2))
+               return true;
+             if (integer_zerop (arg2))
+               return false;
+             if (op2)
+               {
+                 /* This case is meant for ubsan instrumentation.
+                    The caller can check at runtime if *OP2 is
+                    non-zero and OP is null.  */
+                 *op2 = arg2;
+                 return true;
+               }
+             return tree_expr_nonzero_p (arg2);
+           }
+       }
     }
 
   /* If this function is marked as returning non-null, then we can
--- gcc/tree.cc.jj      2024-10-31 08:46:20.767225624 +0100
+++ gcc/tree.cc 2024-11-12 13:54:28.115035591 +0100
@@ -14768,7 +14768,7 @@ get_nonnull_args (const_tree fntype)
   /* 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.  */
+     bitmap means that all arguments have been declared nonnull.  */
   for ( ; attrs; attrs = TREE_CHAIN (attrs))
     {
       attrs = lookup_attribute ("nonnull", attrs);
--- gcc/builtins.cc.jj  2024-11-01 23:03:43.515359648 +0100
+++ gcc/builtins.cc     2024-11-12 18:06:55.850789518 +0100
@@ -1149,6 +1149,24 @@ validate_arglist (const_tree callexpr, .
 
   BITMAP_FREE (argmap);
 
+  if (res)
+    for (tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (TREE_TYPE (fn)));
+        (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+        attrs = TREE_CHAIN (attrs))
+      {
+       tree args = TREE_VALUE (attrs);
+       unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+       unsigned int idx2
+         = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+       if (idx < (unsigned) call_expr_nargs (callexpr)
+           && idx2 < (unsigned) call_expr_nargs (callexpr)
+           && POINTER_TYPE_P (TREE_TYPE (CALL_EXPR_ARG (callexpr, idx)))
+           && integer_zerop (CALL_EXPR_ARG (callexpr, idx))
+           && INTEGRAL_TYPE_P (TREE_TYPE (CALL_EXPR_ARG (callexpr, idx2)))
+           && integer_nonzerop (CALL_EXPR_ARG (callexpr, idx2)))
+         return false;
+      }
+
   return res;
 }
 
--- gcc/tree-ssa-ccp.cc.jj      2024-10-25 10:00:29.536766884 +0200
+++ gcc/tree-ssa-ccp.cc 2024-11-12 14:30:23.322700234 +0100
@@ -155,6 +155,7 @@ along with GCC; see the file COPYING3.
 #include "ipa-cp.h"
 #include "ipa-prop.h"
 #include "internal-fn.h"
+#include "gimple-range.h"
 
 /* Possible lattice values.  */
 typedef enum
@@ -4547,6 +4548,7 @@ unsigned int
 pass_post_ipa_warn::execute (function *fun)
 {
   basic_block bb;
+  gimple_ranger *ranger = NULL;
 
   FOR_EACH_BB_FN (bb, fun)
     {
@@ -4559,13 +4561,12 @@ pass_post_ipa_warn::execute (function *f
 
          tree fntype = gimple_call_fntype (stmt);
          bitmap nonnullargs = get_nonnull_args (fntype);
-         if (!nonnullargs)
-           continue;
 
          tree fndecl = gimple_call_fndecl (stmt);
          const bool closure = fndecl && DECL_LAMBDA_FUNCTION_P (fndecl);
 
-         for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+         for (unsigned i = nonnullargs ? 0 : ~0U;
+              i < gimple_call_num_args (stmt); i++)
            {
              tree arg = gimple_call_arg (stmt, i);
              if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
@@ -4613,8 +4614,67 @@ pass_post_ipa_warn::execute (function *f
                        fndecl, "nonnull");
            }
          BITMAP_FREE (nonnullargs);
+
+         for (tree attrs = TYPE_ATTRIBUTES (fntype);
+              (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+              attrs = TREE_CHAIN (attrs))
+           {
+             tree args = TREE_VALUE (attrs);
+             unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+             unsigned int idx2
+               = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+             if (idx < gimple_call_num_args (stmt)
+                 && idx2 < gimple_call_num_args (stmt))
+               {
+                 tree arg = gimple_call_arg (stmt, idx);
+                 tree arg2 = gimple_call_arg (stmt, idx2);
+                 if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
+                     || !integer_zerop (arg)
+                     || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
+                     || integer_zerop (arg2)
+                     || ((TREE_CODE (fntype) == METHOD_TYPE || closure)
+                         && (idx == 0 || idx2 == 0)))
+                   continue;
+                 if (!integer_nonzerop (arg2)
+                     && !tree_expr_nonzero_p (arg2))
+                   {
+                     if (TREE_CODE (arg2) != SSA_NAME || optimize < 2)
+                       continue;
+                     if (!ranger)
+                       ranger = enable_ranger (cfun);
+
+                     int_range_max vr;
+                     get_range_query (cfun)->range_of_expr (vr, arg2, stmt);
+                     if (range_includes_zero_p (vr))
+                       continue;
+                   }
+                 unsigned argno = idx + 1;
+                 unsigned argno2 = idx2 + 1;
+                 location_t loc = (EXPR_HAS_LOCATION (arg)
+                                   ? EXPR_LOCATION (arg)
+                                   : gimple_location (stmt));
+                 auto_diagnostic_group d;
+
+                 if (!warning_at (loc, OPT_Wnonnull,
+                                  "argument %u null where non-null "
+                                  "expected because argument %u is "
+                                  "nonzero", argno, argno2))
+                   continue;
+
+                 tree fndecl = gimple_call_fndecl (stmt);
+                 if (fndecl && DECL_IS_UNDECLARED_BUILTIN (fndecl))
+                   inform (loc, "in a call to built-in function %qD",
+                           fndecl);
+                 else if (fndecl)
+                   inform (DECL_SOURCE_LOCATION (fndecl),
+                           "in a call to function %qD declared %qs",
+                           fndecl, "nonnull_if_nonzero");
+               }
+           }
        }
     }
+  if (ranger)
+    disable_ranger (cfun);
   return 0;
 }
 
--- gcc/ubsan.cc.jj     2024-10-25 10:00:29.556766598 +0200
+++ gcc/ubsan.cc        2024-11-12 13:01:38.079628478 +0100
@@ -2047,8 +2047,9 @@ instrument_nonnull_arg (gimple_stmt_iter
   for (unsigned int i = 0; i < gimple_call_num_args (stmt); i++)
     {
       tree arg = gimple_call_arg (stmt, i);
+      tree arg2;
       if (POINTER_TYPE_P (TREE_TYPE (arg))
-         && infer_nonnull_range_by_attribute (stmt, arg))
+         && infer_nonnull_range_by_attribute (stmt, arg, &arg2))
        {
          gimple *g;
          if (!is_gimple_val (arg))
@@ -2058,6 +2059,13 @@ instrument_nonnull_arg (gimple_stmt_iter
              gsi_safe_insert_before (gsi, g);
              arg = gimple_assign_lhs (g);
            }
+         if (arg2 && !is_gimple_val (arg2))
+           {
+             g = gimple_build_assign (make_ssa_name (TREE_TYPE (arg2)), arg2);
+             gimple_set_location (g, loc[0]);
+             gsi_safe_insert_before (gsi, g);
+             arg2 = gimple_assign_lhs (g);
+           }
 
          basic_block then_bb, fallthru_bb;
          *gsi = create_cond_insert_point (gsi, true, false, true,
@@ -2069,6 +2077,18 @@ instrument_nonnull_arg (gimple_stmt_iter
          gsi_insert_after (gsi, g, GSI_NEW_STMT);
 
          *gsi = gsi_after_labels (then_bb);
+         if (arg2)
+           {
+             *gsi = create_cond_insert_point (gsi, true, false, true,
+                                              &then_bb, &fallthru_bb);
+             g = gimple_build_cond (NE_EXPR, arg2,
+                                    build_zero_cst (TREE_TYPE (arg2)),
+                                    NULL_TREE, NULL_TREE);
+             gimple_set_location (g, loc[0]);
+             gsi_insert_after (gsi, g, GSI_NEW_STMT);
+
+             *gsi = gsi_after_labels (then_bb);
+           }
          if (flag_sanitize_trap & SANITIZE_NONNULL_ATTRIBUTE)
            g = gimple_build_call (builtin_decl_explicit (BUILT_IN_TRAP), 0);
          else
--- gcc/doc/extend.texi.jj      2024-11-11 20:04:33.377202404 +0100
+++ gcc/doc/extend.texi 2024-11-12 17:54:26.062340704 +0100
@@ -2755,9 +2755,10 @@ object size, for example in functions th
 Note that the @code{access} attribute merely specifies how an object
 referenced by the pointer argument can be accessed; it does not imply that
 an access @strong{will} happen.  Also, the @code{access} attribute does not
-imply the attribute @code{nonnull}; it may be appropriate to add both 
attributes
-at the declaration of a function that unconditionally manipulates a buffer via
-a pointer argument.  See the @code{nonnull} attribute for more information and
+imply the attribute @code{nonnull} nor the attribute @code{nonnull_if_nonzero};
+it may be appropriate to add both attributes at the declaration of a function
+that unconditionally manipulates a buffer via a pointer argument.  See the
+@code{nonnull} or @code{nonnull_if_nonzero} attributes for more information and
 caveats.
 
 @cindex @code{alias} function attribute
@@ -3789,6 +3790,34 @@ my_memcpy (void *dest, const void *src,
         __attribute__((nonnull));
 @end smallexample
 
+@cindex @code{nonnull_if_nonzero} function attribute
+@item nonnull_if_nonzero
+@itemx nonnull_if_nonzero (@var{arg-index}, @var{arg2-index})
+The @code{nonnull_if_nonzero} attribute is a conditional version of the
+@code{nonnull} attribute.  It has two arguments, the first argument
+shall be argument index of a pointer argument which must be in some
+cases non-null and the second argument shall be argument index of an
+integral argument (other than boolean).  If the integral argument is
+zero, the pointer argument can be null, if it is non-zero, the pointer
+argument must not be null.
+
+@smallexample
+extern void *
+my_memcpy (void *dest, const void *src, size_t len)
+        __attribute__((nonnull (1, 2)));
+extern void *
+my_memcpy2 (void *dest, const void *src, size_t len)
+        __attribute__((nonnull_if_nonzero (1, 3),
+                       nonnull_if_nonzero (2, 3)));
+@end smallexample
+
+With these declarations, it is invalid to call
+@code{my_memcpy (NULL, NULL, 0);} or to
+call @code{my_memcpy2 (NULL, NULL, 4);} but it is valid
+to call @code{my_memcpy2 (NULL, NULL, 0);}.  This attribute should be
+used on declarations which have e.g.@: an exception for zero sizes,
+in which case null may be passed.
+
 @cindex @code{noplt} function attribute
 @item noplt
 The @code{noplt} attribute is the counterpart to option @option{-fno-plt}.
--- gcc/c-family/c-attribs.cc.jj        2024-10-25 10:00:29.313770074 +0200
+++ gcc/c-family/c-attribs.cc   2024-11-12 12:07:29.224220859 +0100
@@ -139,6 +139,8 @@ static tree handle_vector_size_attribute
 static tree handle_vector_mask_attribute (tree *, tree, tree, int,
                                          bool *) ATTRIBUTE_NONNULL(3);
 static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
+static tree handle_nonnull_if_nonzero_attribute (tree *, tree, tree, int,
+                                                bool *);
 static tree handle_nonstring_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
 static tree handle_expected_throw_attribute (tree *, tree, tree, int, bool *);
@@ -488,6 +490,8 @@ const struct attribute_spec c_common_gnu
                              handle_tls_model_attribute, NULL },
   { "nonnull",                0, -1, false, true, true, false,
                              handle_nonnull_attribute, NULL },
+  { "nonnull_if_nonzero",     2, 2, false, true, true, false,
+                             handle_nonnull_if_nonzero_attribute, NULL },
   { "nonstring",              0, 0, true, false, false, false,
                              handle_nonstring_attribute, NULL },
   { "nothrow",                0, 0, true,  false, false, false,
@@ -5000,7 +5004,7 @@ handle_nonnull_attribute (tree *node, tr
       /* NEXT is null when the attribute includes just one argument.
         That's used to tell positional_argument to avoid mentioning
         the argument number in diagnostics (since there's just one
-        mentioning it is unnecessary and coule be confusing).  */
+        mentioning it is unnecessary and could be confusing).  */
       tree next = TREE_CHAIN (args);
       if (tree val = positional_argument (type, name, pos, POINTER_TYPE,
                                          next || i > 1 ? i : 0))
@@ -5015,6 +5019,29 @@ handle_nonnull_attribute (tree *node, tr
 
   return NULL_TREE;
 }
+
+/* Handle the "nonnull_if_nonzero" attribute.  */
+
+static tree
+handle_nonnull_if_nonzero_attribute (tree *node, tree name,
+                                    tree args, int ARG_UNUSED (flags),
+                                    bool *no_add_attrs)
+{
+  tree type = *node;
+  tree pos = TREE_VALUE (args);
+  tree pos2 = TREE_VALUE (TREE_CHAIN (args));
+  tree val = positional_argument (type, name, pos, POINTER_TYPE, 1);
+  tree val2 = positional_argument (type, name, pos2, INTEGER_TYPE, 2);
+  if (val && val2)
+    {
+      TREE_VALUE (args) = val;
+      TREE_VALUE (TREE_CHAIN (args)) = val2;
+    }
+  else
+    *no_add_attrs = true;
+
+  return NULL_TREE;
+}
 
 /* Handle the "fd_arg", "fd_arg_read" and "fd_arg_write" attributes */
 
--- gcc/c-family/c-common.cc.jj 2024-11-11 20:04:33.358202671 +0100
+++ gcc/c-family/c-common.cc    2024-11-12 16:53:19.148000499 +0100
@@ -5718,6 +5718,8 @@ struct nonnull_arg_ctx
   /* The function whose arguments are being checked and its type (used
      for calls through function pointers).  */
   const_tree fndecl, fntype;
+  /* For nonnull_if_nonzero, index of the other argument.  */
+  unsigned HOST_WIDE_INT other;
   /* True if a warning has been issued.  */
   bool warned_p;
 };
@@ -5756,23 +5758,19 @@ check_function_nonnull (nonnull_arg_ctx
     }
 
   tree attrs = lookup_attribute ("nonnull", TYPE_ATTRIBUTES (ctx.fntype));
-  if (attrs == NULL_TREE)
-    return ctx.warned_p;
 
   tree a = attrs;
   /* See if any of the nonnull attributes has no arguments.  If so,
      then every pointer argument is checked (in which case the check
      for pointer type is done in check_nonnull_arg).  */
-  if (TREE_VALUE (a) != NULL_TREE)
-    do
-      a = lookup_attribute ("nonnull", TREE_CHAIN (a));
-    while (a != NULL_TREE && TREE_VALUE (a) != NULL_TREE);
+  while (a != NULL_TREE && TREE_VALUE (a) != NULL_TREE)
+    a = lookup_attribute ("nonnull", TREE_CHAIN (a));
 
   if (a != NULL_TREE)
     for (int i = firstarg; i < nargs; i++)
       check_function_arguments_recurse (check_nonnull_arg, &ctx, argarray[i],
                                        i + 1, OPT_Wnonnull);
-  else
+  else if (attrs)
     {
       /* Walk the argument list.  If we encounter an argument number we
         should check for non-null, do it.  */
@@ -5791,6 +5789,28 @@ check_function_nonnull (nonnull_arg_ctx
                                              OPT_Wnonnull);
        }
     }
+  if (a == NULL_TREE)
+    for (attrs = TYPE_ATTRIBUTES (ctx.fntype);
+        (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+        attrs = TREE_CHAIN (attrs))
+      {
+       tree args = TREE_VALUE (attrs);
+       unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+       unsigned int idx2
+         = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+       if (idx < (unsigned) nargs - firstarg
+           && idx2 < (unsigned) nargs - firstarg
+           && INTEGRAL_TYPE_P (TREE_TYPE (argarray[firstarg + idx2]))
+           && integer_nonzerop (argarray[firstarg + idx2]))
+         {
+           ctx.other = firstarg + idx2 + 1;
+           check_function_arguments_recurse (check_nonnull_arg, &ctx,
+                                             argarray[firstarg + idx],
+                                             firstarg + idx + 1,
+                                             OPT_Wnonnull);
+           ctx.other = 0;
+         }
+      }
   return ctx.warned_p;
 }
 
@@ -5972,13 +5992,23 @@ check_nonnull_arg (void *ctx, tree param
     }
   else
     {
-      warned = warning_at (loc, OPT_Wnonnull,
-                          "argument %u null where non-null expected",
-                          (unsigned) param_num);
+      if (pctx->other)
+       warned = warning_at (loc, OPT_Wnonnull,
+                            "argument %u null where non-null expected "
+                            "because argument %u is nonzero",
+                            (unsigned) param_num,
+                            TREE_CODE (pctx->fntype) == METHOD_TYPE
+                            ? (unsigned) pctx->other - 1
+                            : (unsigned) pctx->other);
+      else
+       warned = warning_at (loc, OPT_Wnonnull,
+                            "argument %u null where non-null expected",
+                            (unsigned) param_num);
       if (warned && pctx->fndecl)
        inform (DECL_SOURCE_LOCATION (pctx->fndecl),
                "in a call to function %qD declared %qs",
-               pctx->fndecl, "nonnull");
+               pctx->fndecl,
+               pctx->other ? "nonnull_if_nonzero" : "nonnull");
     }
 
   if (warned)
@@ -6224,7 +6254,7 @@ check_function_arguments (location_t loc
      to do this if format checking is enabled.  */
   if (warn_nonnull)
     {
-      nonnull_arg_ctx ctx = { loc, fndecl, fntype, false };
+      nonnull_arg_ctx ctx = { loc, fndecl, fntype, 0, false };
       warned_p = check_function_nonnull (ctx, nargs, argarray);
     }
 
--- gcc/testsuite/gcc.dg/nonnull-8.c.jj 2024-11-12 15:08:22.138633984 +0100
+++ gcc/testsuite/gcc.dg/nonnull-8.c    2024-11-12 17:02:45.213024228 +0100
@@ -0,0 +1,57 @@
+/* Test for the "nonnull_if_nonzero" function attribute.  */
+/* { dg-do compile } */
+/* { dg-options "-Wnonnull" } */
+
+#include <stddef.h>
+
+extern void func1 (char *, char *, int)
+  __attribute__((nonnull_if_nonzero (1, 3), nonnull_if_nonzero (2, 3)));
+
+extern void func2 (char *, char *, unsigned long)
+  __attribute__((nonnull_if_nonzero (1, 3)));
+
+enum E { E0 = 0, E1 = __INT_MAX__ };
+extern void func3 (char *, int, char *, enum E)
+  __attribute__((nonnull_if_nonzero (1, 4), nonnull_if_nonzero (3, 2)));
+
+extern void func4 (long, char *, char *, long)
+  __attribute__((nonnull_if_nonzero (2, 1)))
+  __attribute__((nonnull_if_nonzero (3, 4)));
+
+void
+foo (int i1, int i2, int i3, char *cp1, char *cp2, char *cp3)
+{
+  func1 (cp1, cp2, i1);
+  func1 (cp1, cp2, 0);
+  func1 (cp1, cp2, 42);
+  func1 (NULL, NULL, 0);
+  func1 (NULL, NULL, i1);
+
+  func1 (NULL, cp2, 42); /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+  func1 (cp1, NULL, 1); /* { dg-warning "argument 2 null where non-null 
expected because argument 3 is nonzero" } */
+
+  func2 (cp1, NULL, 17);
+  func2 (NULL, cp2, 0);
+  func2 (NULL, cp1, 2); /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+
+  func3 (NULL, i2, cp3, i3);
+  func3 (cp1, i2, NULL, i3);
+  func3 (NULL, i2, cp3, E0);
+  func3 (cp1, 0, NULL, E1);
+  func3 (NULL, i2, cp3, E1); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  func3 (cp3, 5, NULL, i3); /* { dg-warning "argument 3 null where non-null 
expected because argument 2 is nonzero" } */
+
+  func1 (i2 ? cp1 : NULL, cp2, i3);
+  func1 (i2 ? NULL : cp1, cp2, i3);
+  func1 (i2 ? (i3 ? cp1 : NULL) : cp2, cp3, i1);
+  func1 (i1 ? cp1 : NULL, cp2, 0);
+  func1 (i1 ? NULL : cp1, cp2, 0);
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 0);
+  func1 (i1 ? cp1 : NULL, cp2, 1); /* { dg-warning "argument 1 null where 
non-null expected because argument 3 is nonzero" } */
+  func1 (i1 ? NULL : cp1, cp2, 2); /* { dg-warning "argument 1 null where 
non-null expected because argument 3 is nonzero" } */
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 3); /* { dg-warning "argument 1 
null where non-null expected because argument 3 is nonzero" } */
+
+  func4 (0, NULL, NULL, 0);
+  func4 (-1, NULL, cp1, 0); /* { dg-warning "argument 2 null where non-null 
expected because argument 1 is nonzero" } */
+  func4 (0, cp1, NULL, 77); /* { dg-warning "argument 3 null where non-null 
expected because argument 4 is nonzero" } */
+}
--- gcc/testsuite/gcc.dg/nonnull-9.c.jj 2024-11-12 15:36:03.014255810 +0100
+++ gcc/testsuite/gcc.dg/nonnull-9.c    2024-11-12 16:01:48.208513109 +0100
@@ -0,0 +1,40 @@
+/* Test for the invalid use of the "nonnull_if_nonzero" function attribute.  */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu17 -pedantic-errors" } */
+
+extern void func1 () __attribute__((nonnull_if_nonzero)); /* { dg-error "wrong 
number of arguments specified for 'nonnull_if_nonzero' attribute" } */
+/* { dg-message "expected 2, found 0" "" { target *-*-* } .-1 } */
+
+extern void func2 (char *) __attribute__((nonnull_if_nonzero(1))); /* { 
dg-error "wrong number of arguments specified for 'nonnull_if_nonzero' 
attribute" } */
+/* { dg-message "expected 2, found 1" "" { target *-*-* } .-1 } */
+
+extern void func3 (char *) __attribute__((nonnull_if_nonzero(1, 2, 3))); /* { 
dg-error "wrong number of arguments specified for 'nonnull_if_nonzero' 
attribute" } */
+/* { dg-message "expected 2, found 3" "" { target *-*-* } .-1 } */
+
+extern void func4 (char *, int) __attribute__((nonnull_if_nonzero(3, 2))); /* 
{ dg-warning "'nonnull_if_nonzero' attribute argument 1 value '3' exceeds the 
number of function parameters 2" } */
+
+extern void func5 (char *, int) __attribute__((nonnull_if_nonzero(1, 3))); /* 
{ dg-warning "nonnull_if_nonzero' attribute argument 2 value '3' exceeds the 
number of function parameters 2" } */
+
+extern void func6 (char *, int) __attribute__((nonnull_if_nonzero (foo, 2))); 
/* { dg-warning ".nonnull_if_nonzero. attribute argument 1 is invalid" } */
+/* { dg-error ".foo. undeclared" "undeclared argument" { target *-*-* } .-1 } 
*/
+
+extern void func7 (char *, int) __attribute__((nonnull_if_nonzero (1, bar))); 
/* { dg-warning ".nonnull_if_nonzero. attribute argument 2 is invalid" } */
+/* { dg-error ".bar. undeclared" "undeclared argument" { target *-*-* } .-1 } 
*/
+
+extern void func8 (int, int) __attribute__((nonnull_if_nonzero(1, 2))); /* { 
dg-warning "'nonnull_if_nonzero' attribute argument 1 value '1' refers to 
parameter type 'int'" } */
+
+extern void func9 (char *, float) __attribute__((nonnull_if_nonzero(1, 2))); 
/* { dg-warning "'nonnull_if_nonzero' attribute argument 2 value '2' refers to 
parameter type 'float'" } */
+
+extern void func10 (char *, _Bool) __attribute__((nonnull_if_nonzero(1, 2))); 
/* { dg-warning "'nonnull_if_nonzero' attribute argument 2 value '2' refers to 
parameter type '_Bool'" } */
+
+extern void func11 (char *, char *) __attribute__((nonnull_if_nonzero(1, 2))); 
/* { dg-warning "'nonnull_if_nonzero' attribute argument 2 value '2' refers to 
parameter type 'char \\\*'" } */
+
+void
+foo (void)
+{
+}
+
+void
+bar (void)
+{
+}
--- gcc/testsuite/gcc.dg/nonnull-10.c.jj        2024-11-12 17:04:10.170826997 
+0100
+++ gcc/testsuite/gcc.dg/nonnull-10.c   2024-11-12 17:36:40.542340272 +0100
@@ -0,0 +1,162 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wnonnull" } */
+
+#define N(x, y) __attribute__ ((nonnull_if_nonzero (x, y)))
+
+void N (1, 2) f1_1 (void *, int);
+
+void N (1, 3) f2_1 (void *, void *, int);
+void N (1, 3) N (2, 3) f2_1_2 (void *, void *, int);
+
+void N (1, 4) N (3, 5) f3_1_3 (void *, void *, void *, int, int);
+
+void N (1, 5) N (2, 5) N (4, 5) g4_1_2_4 (void *, void *, void *, void *, 
long);
+void N (1, 5) N (3, 5) N (4, 5) g4_1_3_4 (void *, void *, void *, void *, 
long);
+void N (2, 5) N (3, 5) N (4, 5) g4_2_3_4 (void *, void *, void *, void *, 
long);
+
+void N (1, 17) N (3, 17) N (5, 17) N (7, 17) N (11, 17) N (13, 17)
+g16_1_3_5_7_11_13 (void *, void *, void *, void *,
+                  void *, void *, void *, void *,
+                  void *, void *, void *, void *,
+                  void *, void *, void *, void *, int);
+
+static void *null (void) { return 0; }
+
+void
+test (int t, long u)
+{
+  void *p0 = null ();
+  void *px = &px;
+
+  f1_1 (p0, 0);
+  f1_1 (p0, t);
+  f1_1 (p0, 42); /* { dg-warning "argument 1 null where non-null expected 
because argument 2 is nonzero" } */
+  if (t)
+    f1_1 (p0, t); /* { dg-warning "argument 1 null where non-null expected 
because argument 2 is nonzero" } */
+  f1_1 (px, 17);
+
+  f2_1 (p0, px, 0);
+  f2_1 (p0, px, t);
+  f2_1 (p0, px, 5);  /* { dg-warning "argument 1 null where non-null expected 
because argument 3 is nonzero" } */
+  if (t > 4)
+    f2_1 (p0, px, t);  /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+  f2_1 (px, p0, 17);
+  f2_1 (p0, p0, 0);
+  if (t < 0)
+    f2_1 (p0, p0, t);  /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+
+  f2_1_2 (p0, p0, 0);
+  f2_1_2 (p0, p0, t);
+  f2_1_2 (p0, px, 1); /* { dg-warning "argument 1 null where non-null expected 
because argument 3 is nonzero" } */
+  if (t > 8)
+    f2_1_2 (p0, px, t); /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+  f2_1_2 (px, p0, -3); /* { dg-warning "argument 2 null where non-null 
expected because argument 3 is nonzero" } */
+  if (t < -2)
+    f2_1_2 (px, p0, t); /* { dg-warning "argument 2 null where non-null 
expected because argument 3 is nonzero" } */
+  f2_1_2 (p0, p0, 8); /* { dg-warning "argument 1 null where non-null expected 
because argument 3 is nonzero" } */
+  /* { dg-warning "argument 2 null where non-null expected because argument 3 
is nonzero" "argument 2" { target *-*-* } .-1 } */
+  if (t > 7)
+    f2_1_2 (p0, p0, t); /* { dg-warning "argument 1 null where non-null 
expected because argument 3 is nonzero" } */
+  /* { dg-warning "argument 2 null where non-null expected because argument 3 
is nonzero" "argument 2" { target *-*-* } .-1 } */
+
+  f3_1_3 (p0, p0, p0, 0, 0);
+  f3_1_3 (p0, p0, px, 0, 6);
+  f3_1_3 (px, p0, p0, 2, 0);
+  f3_1_3 (p0, p0, p0, t, t);
+  f3_1_3 (p0, p0, px, t, 6);
+  f3_1_3 (px, p0, p0, 2, t);
+  f3_1_3 (p0, px, px, 8, 2); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  if (t > 9)
+    f3_1_3 (p0, px, px, t, 3); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  f3_1_3 (px, p0, px, 9, 10);
+  if (t > 11)
+    f3_1_3 (px, p0, px, t, t);
+  f3_1_3 (px, px, p0, 10, 11); /* { dg-warning "argument 3 null where non-null 
expected because argument 5 is nonzero" } */
+  if (t < -5)
+    f3_1_3 (px, px, p0, 0, t); /* { dg-warning "argument 3 null where non-null 
expected because argument 5 is nonzero" } */
+  f3_1_3 (p0, p0, px, 11, 12); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  if (t > 26)
+    f3_1_3 (p0, p0, px, t, 0); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  f3_1_3 (px, p0, p0, 12, 13); /* { dg-warning "argument 3 null where non-null 
expected because argument 5 is nonzero" } */
+  if (t > 31)
+    f3_1_3 (px, p0, p0, 12, t); /* { dg-warning "argument 3 null where 
non-null expected because argument 5 is nonzero" } */
+  f3_1_3 (p0, p0, p0, 13, 14); /* { dg-warning "argument 1 null where non-null 
expected because argument 4 is nonzero" } */
+  /* { dg-warning "argument 3 null where non-null expected because argument 5 
is nonzero" "argument 3" { target *-*-* } .-1 } */
+  if (t > 28)
+    f3_1_3 (p0, p0, p0, t, t + 1); /* { dg-warning "argument 1 null where 
non-null expected because argument 4 is nonzero" } */
+  /* { dg-warning "argument 3 null where non-null expected because argument 5 
is nonzero" "argument 3" { target *-*-* } .-1 } */
+
+  g4_1_2_4 (p0, px, px, px, u);
+  g4_1_2_4 (px, p0, px, px, u);
+  g4_1_2_4 (px, px, p0, px, u);
+  g4_1_2_4 (px, px, px, p0, u);
+  g4_1_2_4 (p0, px, px, px, 0);
+  g4_1_2_4 (px, p0, px, px, 0);
+  g4_1_2_4 (px, px, p0, px, 0);
+  g4_1_2_4 (px, px, px, p0, 0);
+  g4_1_2_4 (p0, px, px, px, 15); /* { dg-warning "argument 1 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u)
+    g4_1_2_4 (p0, px, px, px, u); /* { dg-warning "argument 1 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_1_2_4 (px, p0, px, px, 16); /* { dg-warning "argument 2 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 2)
+    g4_1_2_4 (px, p0, px, px, u); /* { dg-warning "argument 2 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_1_2_4 (px, px, p0, px, 17);
+  if (u > 3)
+    g4_1_2_4 (px, px, p0, px, u);
+  g4_1_2_4 (px, px, px, p0, 18); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u < -2 || u > 10)
+    g4_1_2_4 (px, px, px, p0, u); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+
+  g4_1_3_4 (p0, px, px, px, u);
+  g4_1_3_4 (px, p0, px, px, u);
+  g4_1_3_4 (px, px, p0, px, u);
+  g4_1_3_4 (px, px, px, p0, u);
+  g4_1_3_4 (p0, px, px, px, 0);
+  g4_1_3_4 (px, p0, px, px, 0);
+  g4_1_3_4 (px, px, p0, px, 0);
+  g4_1_3_4 (px, px, px, p0, 0);
+  g4_1_3_4 (p0, px, px, px, 20); /* { dg-warning "argument 1 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 4)
+    g4_1_3_4 (p0, px, px, px, u); /* { dg-warning "argument 1 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_1_3_4 (px, p0, px, px, 21);
+  if (u > 6 || u < -24)
+    g4_1_3_4 (px, p0, px, px, u);
+  g4_1_3_4 (px, px, p0, px, 22); /* { dg-warning "argument 3 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 9)
+    g4_1_3_4 (px, px, p0, px, u - 3); /* { dg-warning "argument 3 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_1_3_4 (px, px, px, p0, 23); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 10)
+    g4_1_3_4 (px, px, px, p0, u); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+
+  g4_2_3_4 (p0, px, px, px, u);
+  g4_2_3_4 (px, p0, px, px, u);
+  g4_2_3_4 (px, px, p0, px, u);
+  g4_2_3_4 (px, px, px, p0, u);
+  g4_2_3_4 (p0, px, px, px, 0);
+  g4_2_3_4 (px, p0, px, px, 0);
+  g4_2_3_4 (px, px, p0, px, 0);
+  g4_2_3_4 (px, px, px, p0, 0);
+  g4_2_3_4 (p0, px, px, px, 1);
+  if (u > 12)
+    g4_2_3_4 (p0, px, px, px, u);
+  g4_2_3_4 (px, p0, px, px, 2); /* { dg-warning "argument 2 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 17)
+    g4_2_3_4 (px, p0, px, px, u - 3); /* { dg-warning "argument 2 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_2_3_4 (px, px, p0, px, 3); /* { dg-warning "argument 3 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 24)
+    g4_2_3_4 (px, px, p0, px, u); /* { dg-warning "argument 3 null where 
non-null expected because argument 5 is nonzero" } */
+  g4_2_3_4 (px, px, px, p0, 4); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+  if (u > 42)
+    g4_2_3_4 (px, px, px, p0, u); /* { dg-warning "argument 4 null where 
non-null expected because argument 5 is nonzero" } */
+
+  g16_1_3_5_7_11_13 (px, px, px, px, px, px, px, px,
+                    px, px, px, px, px, px, px, px, 17);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, t);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, 0);
+
+  g16_1_3_5_7_11_13 (px, p0, px, p0, px, p0, px, p0, p0, p0, px, p0, p0, p0, 
p0, p0, 2); /* { dg-warning "argument 13 null where non-null expected because 
argument 17 is nonzero" } */
+  if (t > 122)
+    g16_1_3_5_7_11_13 (px, p0, px, p0, px, p0, px, p0, p0, p0, px, p0, p0, p0, 
p0, p0, t); /* { dg-warning "argument 13 null where non-null expected because 
argument 17 is nonzero" } */
+}
--- gcc/testsuite/c-c++-common/ubsan/nonnull-6.c.jj     2024-11-12 
13:25:56.070137497 +0100
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-6.c        2024-11-12 
13:26:03.996025915 +0100
@@ -0,0 +1,27 @@
+/* { dg-do run } */
+/* { dg-options "-fsanitize=undefined -fno-sanitize-recover=undefined" } */
+
+__attribute__((noipa, nonnull_if_nonzero (1, 4)))
+__attribute__((nonnull (3), nonnull_if_nonzero (5, 2))) void
+foo (void *a, unsigned long b, void *c, int d, void *e)
+{
+  (void) a;
+  (void) b;
+  (void) c;
+  (void) d;
+  (void) e;
+}
+
+__attribute__((noipa))
+void
+bar (void *a, unsigned long b, void *c, int d, void *e)
+{
+  foo (a, b, c, d, e);
+}
+
+int
+main ()
+{
+  bar ("", 42, "", 1, "");
+  bar (0, 0, "", 0, 0);
+}
--- gcc/testsuite/c-c++-common/ubsan/nonnull-7.c.jj     2024-11-12 
13:26:12.436907082 +0100
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-7.c        2024-11-12 
13:31:02.020830219 +0100
@@ -0,0 +1,38 @@
+/* { dg-do run } */
+/* { dg-options "-fsanitize=nonnull-attribute" } */
+
+__attribute__((noipa, nonnull_if_nonzero (1, 4)))
+__attribute__((nonnull (3), nonnull_if_nonzero (5, 2))) void
+foo (void *a, unsigned long b, void *c, int d, void *e)
+{
+  (void) a;
+  (void) b;
+  (void) c;
+  (void) d;
+  (void) e;
+}
+
+__attribute__((noipa))
+void
+bar (void *a, unsigned long b, void *c, int d, void *e)
+{
+  foo (a, b, c, d, e);
+}
+
+int
+main ()
+{
+  bar ("", 42, 0, 1, "");
+  bar (0, 25, "", 7, "");
+  bar ("", -82, "", 68, 0);
+  foo ("", 42, 0, 1, "");
+  foo (0, 25, "", 7, "");
+  foo ("", -82, "", 68, 0);
+}
+
+/* { dg-output "\.c:19:\[0-9]*:\[^\n\r]*null pointer passed as argument 3, 
which is declared to never be null\[^\n\r]*(\n|\r\n|\r)" } */
+/* { dg-output "\[^\n\r]*\.c:19:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 1, which is declared to never be null\[^\n\r]*(\n|\r\n|\r)" } */
+/* { dg-output "\[^\n\r]*\.c:19:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 5, which is declared to never be null\[^\n\r]*(\n|\r\n|\r)" } */
+/* { dg-output "\[^\n\r]*\.c:28:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 3, which is declared to never be null\[^\n\r]*(\n|\r\n|\r)" } */
+/* { dg-output "\[^\n\r]*\.c:29:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 1, which is declared to never be null\[^\n\r]*(\n|\r\n|\r)" } */
+/* { dg-output "\[^\n\r]*\.c:30:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 5, which is declared to never be null" } */

        Jakub

Reply via email to