On Tue, Nov 12, 2024 at 06:34:39PM +0100, Jakub Jelinek wrote:
> What do you think about this?  So far lightly tested.

Unfortunately bootstrap/regtest revealed some issues in the patch,
the tree-ssa-ccp.cc changes break bootstrap because fntype in there
may be NULL and that is what get_nonnull_args handles by just returning
NULL, but obviously TYPE_ATTRIBUTES (fntype) can't be accessed, so I've
added if (!fntype) continue;
And the ubsan tests worked in C but not C++ due to extra warning, so I've
adjusted them.

This has been successfully bootstrapped/regtested on x86_64-linux and
i686-linux.

2024-11-13  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 23:31:51.290755985 +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)
     {
@@ -4558,14 +4560,15 @@ pass_post_ipa_warn::execute (function *f
            continue;
 
          tree fntype = gimple_call_fntype (stmt);
-         bitmap nonnullargs = get_nonnull_args (fntype);
-         if (!nonnullargs)
+         if (!fntype)
            continue;
+         bitmap nonnullargs = get_nonnull_args (fntype);
 
          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 +4616,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 
23:16:27.686866970 +0100
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-6.c        2024-11-13 
09:13:05.077160640 +0100
@@ -0,0 +1,28 @@
+/* { 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 ()
+{
+  char x;
+  bar (&x, 42, &x, 1, &x);
+  bar (0, 0, &x, 0, 0);
+}
--- gcc/testsuite/c-c++-common/ubsan/nonnull-7.c.jj     2024-11-12 
23:16:27.686866970 +0100
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-7.c        2024-11-13 
09:13:45.732591954 +0100
@@ -0,0 +1,39 @@
+/* { 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 ()
+{
+  char x;
+  bar (&x, 42, 0, 1, &x);
+  bar (0, 25, &x, 7, &x);
+  bar (&x, -82, &x, 68, 0);
+  foo (&x, 42, 0, 1, &x);
+  foo (0, 25, &x, 7, &x);
+  foo (&x, -82, &x, 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:29:\[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:30:\[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:31:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 5, which is declared to never be null" } */

        Jakub

Reply via email to