Hi!

C2Y voted in the
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3466.pdf
paper, which clarifies some of the conditional nonnull cases.
For strncat/__strncat_chk no changes are necessary, we already
use __attribute__((nonnull (1), nonnull_if_nonzero (2, 3))) attributes
on the builtin and glibc can do the same too, meaning that first
argument must be nonnull always and second must be nonnull if
the third one is nonzero.

The problem is with the fread/fwrite changes, where the paper adds:
 If size or nmemb is zero,
+ptr may be a null pointer,
 fread returns zero and the contents of the array and the state of
 the stream remain unchanged.
and ditto for fwrite, so the two argument nonnull_if_nonzero attribute
isn't usable to express that, because whether the pointer can be null
depends on 2 integral arguments rather than one.

The following patch extends the nonnull_if_nonzero attribute, so that
instead of requiring 2 arguments it allows 2 or 3, the first one
is still the pointer argument index which sometimes must not be null
and the other one or two are integral arguments, if there are 2, the
invalid case is only if pointer is null and both the integral arguments
are nonzero.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2025-06-04  Jakub Jelinek  <ja...@redhat.com>

        PR c/120520
        PR c/117023
gcc/
        * builtin-attrs.def (DEF_LIST_INT_INT_INT): Define it and
        use for 1,2,3.
        (ATTR_NONNULL_IF123_LIST): New DEF_ATTR_TREE_LIST.
        (ATTR_NONNULL_4_IF123_LIST): Likewise.
        * builtins.def (BUILT_IN_FWRITE): Use ATTR_NONNULL_4_IF123_LIST
        instead of ATTR_NONNULL_LIST.
        (BUILT_IN_FWRITE_UNLOCKED): Likewise.
        * gimple.h (infer_nonnull_range_by_attribute): Add another optional
        tree * argument defaulted to NULL.
        * gimple.cc (infer_nonnull_range_by_attribute): Add OP3 argument,
        handle 3 argument nonnull_if_nonzero attribute.
        * builtins.cc (validate_arglist): Handle 3 argument nonnull_if_nonzero
        attribute.
        * tree-ssa-ccp.cc (pass_post_ipa_warn::execute): Likewise.
        * ubsan.cc (instrument_nonnull_arg): Adjust
        infer_nonnull_range_by_attribute caller, handle 3 argument
        nonnull_if_nonzero attribute.
        * gimple-range-infer.cc (gimple_infer_range::gimple_infer_range):
        Handle 3 argument nonnull_if_nonzero attribute.
        * doc/extend.texi (nonnull_if_nonzero): Document 3 argument version
        of the attribute.
gcc/c-family/
        * c-attribs.cc (c_common_gnu_attributes): Allow 2 or 3 arguments for
        nonnull_if_nonzero attribute instead of only 2.
        (handle_nonnull_if_nonzero_attribute): Handle 3 argument
        nonnull_if_nonzero.
        * c-common.cc (struct nonnull_arg_ctx): Rename other member to other1,
        add other2 member.
        (check_function_nonnull): Clear a if nonnull attribute has an
        argument.  Adjust for nonnull_arg_ctx changes.  Handle 3 argument
        nonnull_if_nonzero attribute.
        (check_nonnull_arg): Adjust for nonnull_arg_ctx changes, emit different
        diagnostics for 3 argument nonnull_if_nonzero attributes.
        (check_function_arguments): Adjust ctx var initialization.
gcc/analyzer/
        * sm-malloc.cc (malloc_state_machine::on_stmt): Handle 3 argument
        nonnull_if_nonzero attribute.
gcc/testsuite/
        * gcc.dg/nonnull-9.c: Tweak for 3 argument nonnull_if_nonzero
        attribute support, add further tests.
        * gcc.dg/nonnull-12.c: New test.
        * gcc.dg/nonnull-13.c: New test.
        * gcc.dg/nonnull-14.c: New test.
        * c-c++-common/ubsan/nonnull-8.c: New test.
        * c-c++-common/ubsan/nonnull-9.c: New test.

--- gcc/builtin-attrs.def.jj    2025-04-08 14:08:48.243323740 +0200
+++ gcc/builtin-attrs.def       2025-06-03 10:16:02.178728866 +0200
@@ -90,6 +90,14 @@ DEF_LIST_INT_INT (5,0)
 DEF_LIST_INT_INT (5,6)
 #undef DEF_LIST_INT_INT
 
+/* Construct a tree for a list of three integers.  */
+#define DEF_LIST_INT_INT_INT(VALUE1, VALUE2, VALUE3)                    \
+  DEF_ATTR_TREE_LIST (ATTR_LIST_##VALUE1##_##VALUE2##_##VALUE3,                
 \
+                     ATTR_NULL, ATTR_##VALUE1,                          \
+                     ATTR_LIST_##VALUE2##_##VALUE3)
+DEF_LIST_INT_INT_INT (1,2,3)
+#undef DEF_LIST_INT_INT_INT
+
 /* Construct trees for identifiers used in built-in function attributes.
    The construction contributes to startup costs so only attributes that
    are used to define built-ins should be defined here.  */
@@ -209,6 +217,12 @@ DEF_ATTR_TREE_LIST (ATTR_NONNULL_1, ATTR
 DEF_ATTR_TREE_LIST (ATTR_NONNULL_2, ATTR_NONNULL, ATTR_LIST_2, ATTR_NULL)
 /* Functions whose third parameter is a nonnull pointer.  */
 DEF_ATTR_TREE_LIST (ATTR_NONNULL_3, ATTR_NONNULL, ATTR_LIST_3, ATTR_NULL)
+/* Functions whose selected pointer parameter(s) are conditionally
+   nonnull.  */
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_IF123_LIST, ATTR_NONNULL_IF_NONZERO, \
+                       ATTR_LIST_1_2_3, ATTR_NULL)
+DEF_ATTR_TREE_LIST (ATTR_NONNULL_4_IF123_LIST, ATTR_NONNULL, \
+                       ATTR_LIST_4, ATTR_NONNULL_IF123_LIST)
 /* Nothrow functions with the sentinel(1) attribute. */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_SENTINEL_1, ATTR_SENTINEL, ATTR_LIST_1, \
                        ATTR_NOTHROW_LIST)
--- gcc/builtins.def.jj 2025-05-20 08:14:03.564445258 +0200
+++ gcc/builtins.def    2025-06-03 10:16:20.068493869 +0200
@@ -920,8 +920,8 @@ DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPUTC_U
 DEF_LIB_BUILTIN        (BUILT_IN_FPUTS, "fputs", 
BT_FN_INT_CONST_STRING_FILEPTR, ATTR_NONNULL_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_FPUTS_UNLOCKED, "fputs_unlocked", 
BT_FN_INT_CONST_STRING_FILEPTR, ATTR_NONNULL_LIST)
 DEF_LIB_BUILTIN        (BUILT_IN_FSCANF, "fscanf", 
BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_FORMAT_SCANF_2_3)
-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_FWRITE, "fwrite", 
BT_FN_SIZE_CONST_PTR_SIZE_SIZE_FILEPTR, ATTR_NONNULL_4_IF123_LIST)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_FWRITE_UNLOCKED, "fwrite_unlocked", 
BT_FN_SIZE_CONST_PTR_SIZE_SIZE_FILEPTR, ATTR_NONNULL_4_IF123_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_NONNULL_1_FORMAT_PRINTF_1_2)
 DEF_LIB_BUILTIN        (BUILT_IN_PUTCHAR, "putchar", BT_FN_INT_INT, ATTR_NULL)
--- gcc/gimple.h.jj     2025-05-20 08:14:06.192409154 +0200
+++ gcc/gimple.h        2025-06-03 10:39:35.545114719 +0200
@@ -1667,7 +1667,8 @@ 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, tree * = NULL);
+extern bool infer_nonnull_range_by_attribute (gimple *, tree, tree * = NULL,
+                                             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    2025-04-28 09:04:29.042893846 +0200
+++ gcc/gimple.cc       2025-06-03 11:48:34.815599375 +0200
@@ -3154,16 +3154,20 @@ infer_nonnull_range_by_dereference (gimp
 }
 
 /* Return true if OP can be inferred to be a non-NULL after STMT
-   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 and return true (in that case returning true doesn't mean
-   OP can be unconditionally inferred to be non-NULL, but conditionally).  */
+   executes by using attributes.  If OP2 and OP3 are non-NULL and
+   nonnull_if_nonzero is the only attribute implying OP being non-NULL
+   and the corresponding argument(s) aren't non-zero INTEGER_CST, set *OP2
+   and *OP3 to the corresponding arguments and return true (in that case
+   returning true doesn't mean OP can be unconditionally inferred to be
+   non-NULL, but conditionally).  */
 bool
-infer_nonnull_range_by_attribute (gimple *stmt, tree op, tree *op2)
+infer_nonnull_range_by_attribute (gimple *stmt, tree op, tree *op2, tree *op3)
 {
   if (op2)
-    *op2 = NULL_TREE;
+    {
+      *op2 = NULL_TREE;
+      *op3 = NULL_TREE;
+    }
 
   /* We can only assume that a pointer dereference will yield
      non-NULL if -fdelete-null-pointer-checks is enabled.  */
@@ -3220,26 +3224,33 @@ infer_nonnull_range_by_attribute (gimple
          unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
          unsigned int idx2
            = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+         unsigned int idx3 = idx2;
+         if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+           idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
          if (idx < gimple_call_num_args (stmt)
              && idx2 < gimple_call_num_args (stmt)
+             && idx3 < 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)))
+             tree arg3 = gimple_call_arg (stmt, idx3);
+             if (!INTEGRAL_TYPE_P (TREE_TYPE (arg2))
+                 || !INTEGRAL_TYPE_P (TREE_TYPE (arg3)))
                return false;
-             if (integer_nonzerop (arg2))
+             if (integer_nonzerop (arg2) && integer_nonzerop (arg3))
                return true;
-             if (integer_zerop (arg2))
+             if (integer_zerop (arg2) || integer_zerop (arg3))
                return false;
              if (op2)
                {
                  /* This case is meant for ubsan instrumentation.
-                    The caller can check at runtime if *OP2 is
+                    The caller can check at runtime if *OP2 and *OP3 are
                     non-zero and OP is null.  */
                  *op2 = arg2;
+                 *op3 = arg3;
                  return true;
                }
-             return tree_expr_nonzero_p (arg2);
+             return tree_expr_nonzero_p (arg2) && tree_expr_nonzero_p (arg3);
            }
        }
     }
--- gcc/builtins.cc.jj  2025-05-20 08:14:03.563445272 +0200
+++ gcc/builtins.cc     2025-06-03 11:48:34.747600257 +0200
@@ -1158,12 +1158,18 @@ validate_arglist (const_tree callexpr, .
        unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
        unsigned int idx2
          = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+       unsigned int idx3 = idx2;
+       if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+         idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
        if (idx < (unsigned) call_expr_nargs (callexpr)
            && idx2 < (unsigned) call_expr_nargs (callexpr)
+           && idx3 < (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)))
+           && integer_nonzerop (CALL_EXPR_ARG (callexpr, idx2))
+           && INTEGRAL_TYPE_P (TREE_TYPE (CALL_EXPR_ARG (callexpr, idx3)))
+           && integer_nonzerop (CALL_EXPR_ARG (callexpr, idx3)))
          return false;
       }
 
--- gcc/tree-ssa-ccp.cc.jj      2025-04-08 14:09:29.281752432 +0200
+++ gcc/tree-ssa-ccp.cc 2025-06-03 11:48:34.747600257 +0200
@@ -4624,17 +4624,24 @@ pass_post_ipa_warn::execute (function *f
              unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
              unsigned int idx2
                = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+             unsigned int idx3 = idx2;
+             if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+               idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
              if (idx < gimple_call_num_args (stmt)
-                 && idx2 < gimple_call_num_args (stmt))
+                 && idx2 < gimple_call_num_args (stmt)
+                 && idx3 < gimple_call_num_args (stmt))
                {
                  tree arg = gimple_call_arg (stmt, idx);
                  tree arg2 = gimple_call_arg (stmt, idx2);
+                 tree arg3 = gimple_call_arg (stmt, idx3);
                  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
                      || !integer_zerop (arg)
                      || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
+                     || !INTEGRAL_TYPE_P (TREE_TYPE (arg3))
                      || integer_zerop (arg2)
+                     || integer_zerop (arg3)
                      || ((TREE_CODE (fntype) == METHOD_TYPE || closure)
-                         && (idx == 0 || idx2 == 0)))
+                         && (idx == 0 || idx2 == 0 || idx3 == 0)))
                    continue;
                  if (!integer_nonzerop (arg2)
                      && !tree_expr_nonzero_p (arg2))
@@ -4649,17 +4656,40 @@ pass_post_ipa_warn::execute (function *f
                      if (range_includes_zero_p (vr))
                        continue;
                    }
+                 if (idx2 != idx3
+                     && !integer_nonzerop (arg3)
+                     && !tree_expr_nonzero_p (arg3))
+                   {
+                     if (TREE_CODE (arg3) != SSA_NAME || optimize < 2)
+                       continue;
+                     if (!ranger)
+                       ranger = enable_ranger (cfun);
+
+                     int_range_max vr;
+                     get_range_query (cfun)->range_of_expr (vr, arg3, stmt);
+                     if (range_includes_zero_p (vr))
+                       continue;
+                   }
                  unsigned argno = idx + 1;
                  unsigned argno2 = idx2 + 1;
+                 unsigned argno3 = idx3 + 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))
+                 if (idx2 != idx3)
+                   {
+                     if (!warning_at (loc, OPT_Wnonnull,
+                                      "argument %u null where non-null "
+                                      "expected because arguments %u and %u "
+                                      "are nonzero", argno, argno2, argno3))
+                       continue;
+                   }
+                 else 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);
--- gcc/ubsan.cc.jj     2025-04-08 14:09:38.516623871 +0200
+++ gcc/ubsan.cc        2025-06-03 11:48:34.747600257 +0200
@@ -2046,9 +2046,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;
+      tree arg2, arg3;
       if (POINTER_TYPE_P (TREE_TYPE (arg))
-         && infer_nonnull_range_by_attribute (stmt, arg, &arg2))
+         && infer_nonnull_range_by_attribute (stmt, arg, &arg2, &arg3))
        {
          gimple *g;
          if (!is_gimple_val (arg))
@@ -2058,6 +2058,8 @@ instrument_nonnull_arg (gimple_stmt_iter
              gsi_safe_insert_before (gsi, g);
              arg = gimple_assign_lhs (g);
            }
+         if (arg2 == arg3)
+           arg3 = NULL_TREE;
          if (arg2 && !is_gimple_val (arg2))
            {
              g = gimple_build_assign (make_ssa_name (TREE_TYPE (arg2)), arg2);
@@ -2065,6 +2067,13 @@ instrument_nonnull_arg (gimple_stmt_iter
              gsi_safe_insert_before (gsi, g);
              arg2 = gimple_assign_lhs (g);
            }
+         if (arg3 && !is_gimple_val (arg3))
+           {
+             g = gimple_build_assign (make_ssa_name (TREE_TYPE (arg3)), arg3);
+             gimple_set_location (g, loc[0]);
+             gsi_safe_insert_before (gsi, g);
+             arg3 = gimple_assign_lhs (g);
+           }
 
          basic_block then_bb, fallthru_bb;
          *gsi = create_cond_insert_point (gsi, true, false, true,
@@ -2085,6 +2094,18 @@ instrument_nonnull_arg (gimple_stmt_iter
                                     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 (arg3)
+           {
+             *gsi = create_cond_insert_point (gsi, true, false, true,
+                                              &then_bb, &fallthru_bb);
+             g = gimple_build_cond (NE_EXPR, arg3,
+                                    build_zero_cst (TREE_TYPE (arg3)),
+                                    NULL_TREE, NULL_TREE);
+             gimple_set_location (g, loc[0]);
+             gsi_insert_after (gsi, g, GSI_NEW_STMT);
 
              *gsi = gsi_after_labels (then_bb);
            }
--- gcc/gimple-range-infer.cc.jj        2025-05-02 19:40:40.694738579 +0200
+++ gcc/gimple-range-infer.cc   2025-06-03 11:48:34.774599906 +0200
@@ -197,23 +197,41 @@ gimple_infer_range::gimple_infer_range (
            unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
            unsigned int idx2
              = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+           unsigned int idx3 = idx2;
+           if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+             idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
            if (idx < gimple_call_num_args (s)
-               && idx2 < gimple_call_num_args (s))
+               && idx2 < gimple_call_num_args (s)
+               && idx3 < gimple_call_num_args (s))
              {
                tree arg = gimple_call_arg (s, idx);
                tree arg2 = gimple_call_arg (s, idx2);
+               tree arg3 = gimple_call_arg (s, idx3);
                if (!POINTER_TYPE_P (TREE_TYPE (arg))
                    || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
-                   || integer_zerop (arg2))
+                   || !INTEGRAL_TYPE_P (TREE_TYPE (arg3))
+                   || integer_zerop (arg2)
+                   || integer_zerop (arg3))
                  continue;
-               if (integer_nonzerop (arg2))
+               if (integer_nonzerop (arg2) && integer_nonzerop (arg3))
                  add_nonzero (arg);
                else
                  {
                    value_range r (TREE_TYPE (arg2));
                    if (q->range_of_expr (r, arg2, s)
                        && !r.contains_p (build_zero_cst (TREE_TYPE (arg2))))
-                     add_nonzero (arg);
+                     {
+                       if (idx2 == idx3)
+                         add_nonzero (arg);
+                       else
+                         {
+                           value_range r2 (TREE_TYPE (arg3));
+                           tree zero3 = build_zero_cst (TREE_TYPE (arg3));
+                           if (q->range_of_expr (r2, arg3, s)
+                               && !r2.contains_p (zero3))
+                             add_nonzero (arg);
+                         }
+                     }
                  }
              }
          }
--- gcc/doc/extend.texi.jj      2025-06-02 11:00:15.135408947 +0200
+++ gcc/doc/extend.texi 2025-06-03 14:46:45.225122631 +0200
@@ -2887,13 +2887,18 @@ my_memcpy (void *dest, const void *src,
 @cindex @code{nonnull_if_nonzero} function attribute
 @item nonnull_if_nonzero
 @itemx nonnull_if_nonzero (@var{arg-index}, @var{arg2-index})
+@itemx nonnull_if_nonzero (@var{arg-index}, @var{arg2-index}, @var{arg3-index})
 The @code{nonnull_if_nonzero} attribute is a conditional version of the
-@code{nonnull} attribute.  It has two arguments, the first argument
+@code{nonnull} attribute.  It has two or three 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.
+argument must not be null.  If three arguments are provided, the third
+argument shall be argument index of another integral argument (other than
+boolean) and the pointer argument can be null if either of the integral
+arguments are zero and if both are non-zero, the pointer argument must not
+be null.
 
 @smallexample
 extern void *
@@ -2903,12 +2908,21 @@ extern void *
 my_memcpy2 (void *dest, const void *src, size_t len)
         __attribute__((nonnull_if_nonzero (1, 3),
                        nonnull_if_nonzero (2, 3)));
+extern size_t
+my_fread (void *buf, size_t size, size_t count, FILE *stream)
+        __attribute__((nonnull (4),
+                       nonnull_if_nonzero (1, 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
+call @code{my_memcpy2 (NULL, NULL, 4);} or to call
+@code{my_fread(@var{buf}, 0, 0, NULL);} or to call
+@code{my_fread(NULL, 1, 1, @var{stream});} but it is valid
+to call @code{my_memcpy2 (NULL, NULL, 0);} or
+@code{my_fread(NULL, 0, 0, @var{stream});} or
+@code{my_fread(NULL, 0, 1, @var{stream});} or
+@code{my_fread(NULL, 1, 0, @var{stream});}.  This attribute should be
 used on declarations which have e.g.@: an exception for zero sizes,
 in which case null may be passed.
 
--- gcc/c-family/c-attribs.cc.jj        2025-04-08 14:08:48.329322543 +0200
+++ gcc/c-family/c-attribs.cc   2025-06-03 13:17:06.990640950 +0200
@@ -489,7 +489,7 @@ 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,
+  { "nonnull_if_nonzero",     2, 3, false, true, true, false,
                              handle_nonnull_if_nonzero_attribute, NULL },
   { "nonstring",              0, 0, true, false, false, false,
                              handle_nonstring_attribute, NULL },
@@ -5034,12 +5034,21 @@ handle_nonnull_if_nonzero_attribute (tre
   tree type = *node;
   tree pos = TREE_VALUE (args);
   tree pos2 = TREE_VALUE (TREE_CHAIN (args));
+  tree chain2 = TREE_CHAIN (TREE_CHAIN (args));
+  tree pos3 = NULL_TREE;
+  if (chain2)
+    pos3 = TREE_VALUE (chain2);
   tree val = positional_argument (type, name, pos, POINTER_TYPE, 1);
   tree val2 = positional_argument (type, name, pos2, INTEGER_TYPE, 2);
-  if (val && val2)
+  tree val3 = NULL_TREE;
+  if (chain2)
+    val3 = positional_argument (type, name, pos3, INTEGER_TYPE, 3);
+  if (val && val2 && (!chain2 || val3))
     {
       TREE_VALUE (args) = val;
       TREE_VALUE (TREE_CHAIN (args)) = val2;
+      if (chain2)
+       TREE_VALUE (chain2) = val3;
     }
   else
     *no_add_attrs = true;
--- gcc/c-family/c-common.cc.jj 2025-06-02 11:00:14.181421315 +0200
+++ gcc/c-family/c-common.cc    2025-06-03 12:54:34.900016089 +0200
@@ -5749,8 +5749,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;
+  /* For nonnull_if_nonzero, index of the other arguments.  */
+  unsigned HOST_WIDE_INT other1, other2;
   /* True if a warning has been issued.  */
   bool warned_p;
 };
@@ -5818,6 +5818,7 @@ check_function_nonnull (nonnull_arg_ctx
            check_function_arguments_recurse (check_nonnull_arg, &ctx,
                                              argarray[i], i + 1,
                                              OPT_Wnonnull);
+         a = NULL_TREE;
        }
     }
   if (a == NULL_TREE)
@@ -5829,17 +5830,25 @@ check_function_nonnull (nonnull_arg_ctx
        unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
        unsigned int idx2
          = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+       unsigned int idx3 = idx2;
+       if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+         idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
        if (idx < (unsigned) nargs - firstarg
            && idx2 < (unsigned) nargs - firstarg
+           && idx3 < (unsigned) nargs - firstarg
            && INTEGRAL_TYPE_P (TREE_TYPE (argarray[firstarg + idx2]))
-           && integer_nonzerop (argarray[firstarg + idx2]))
+           && integer_nonzerop (argarray[firstarg + idx2])
+           && INTEGRAL_TYPE_P (TREE_TYPE (argarray[firstarg + idx3]))
+           && integer_nonzerop (argarray[firstarg + idx3]))
          {
-           ctx.other = firstarg + idx2 + 1;
+           ctx.other1 = firstarg + idx2 + 1;
+           ctx.other2 = firstarg + idx3 + 1;
            check_function_arguments_recurse (check_nonnull_arg, &ctx,
                                              argarray[firstarg + idx],
                                              firstarg + idx + 1,
                                              OPT_Wnonnull);
-           ctx.other = 0;
+           ctx.other1 = 0;
+           ctx.other2 = 0;
          }
       }
   return ctx.warned_p;
@@ -6023,14 +6032,25 @@ check_nonnull_arg (void *ctx, tree param
     }
   else
     {
-      if (pctx->other)
+      if (pctx->other1 && pctx->other2 != pctx->other1)
+       warned = warning_at (loc, OPT_Wnonnull,
+                            "argument %u null where non-null expected "
+                            "because arguments %u and %u are nonzero",
+                            (unsigned) param_num,
+                            TREE_CODE (pctx->fntype) == METHOD_TYPE
+                            ? (unsigned) pctx->other1 - 1
+                            : (unsigned) pctx->other1,
+                            TREE_CODE (pctx->fntype) == METHOD_TYPE
+                            ? (unsigned) pctx->other2 - 1
+                            : (unsigned) pctx->other2);
+      else if (pctx->other1)
        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);
+                            ? (unsigned) pctx->other1 - 1
+                            : (unsigned) pctx->other1);
       else
        warned = warning_at (loc, OPT_Wnonnull,
                             "argument %u null where non-null expected",
@@ -6039,7 +6059,7 @@ check_nonnull_arg (void *ctx, tree param
        inform (DECL_SOURCE_LOCATION (pctx->fndecl),
                "in a call to function %qD declared %qs",
                pctx->fndecl,
-               pctx->other ? "nonnull_if_nonzero" : "nonnull");
+               pctx->other1 ? "nonnull_if_nonzero" : "nonnull");
     }
 
   if (warned)
@@ -6295,7 +6315,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, 0, false };
+      nonnull_arg_ctx ctx = { loc, fndecl, fntype, 0, 0, false };
       warned_p = check_function_nonnull (ctx, nargs, argarray);
     }
 
--- gcc/analyzer/sm-malloc.cc.jj        2025-05-01 08:25:43.965938983 +0200
+++ gcc/analyzer/sm-malloc.cc   2025-06-03 13:49:57.469237200 +0200
@@ -2172,19 +2172,27 @@ malloc_state_machine::on_stmt (sm_contex
                  unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
                  unsigned int idx2
                    = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+                 unsigned int idx3 = idx2;
+                 if (tree chain2 = TREE_CHAIN (TREE_CHAIN (args)))
+                   idx3 = TREE_INT_CST_LOW (TREE_VALUE (chain2)) - 1;
                  if (idx < gimple_call_num_args (stmt)
-                     && idx2 < gimple_call_num_args (stmt))
+                     && idx2 < gimple_call_num_args (stmt)
+                     && idx3 < gimple_call_num_args (stmt))
                    {
                      tree arg = gimple_call_arg (stmt, idx);
                      tree arg2 = gimple_call_arg (stmt, idx2);
+                     tree arg3 = gimple_call_arg (stmt, idx3);
                      if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
                          || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
-                         || integer_zerop (arg2))
+                         || !INTEGRAL_TYPE_P (TREE_TYPE (arg3))
+                         || integer_zerop (arg2)
+                         || integer_zerop (arg3))
                        continue;
-                     if (integer_nonzerop (arg2))
+                     if (integer_nonzerop (arg2) && integer_nonzerop (arg3))
                        ;
                      else
-                       /* FIXME: Use ranger here to query arg2 range?  */
+                       /* FIXME: Use ranger here to query arg2 and arg3
+                          ranges?  */
                        continue;
                      handle_nonnull (sm_ctxt, node, stmt, fndecl, arg, idx);
                    }
--- gcc/testsuite/gcc.dg/nonnull-9.c.jj 2025-04-08 14:09:02.000000000 +0200
+++ gcc/testsuite/gcc.dg/nonnull-9.c    2025-06-03 13:12:51.317981665 +0200
@@ -3,31 +3,42 @@
 /* { 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 } */
+/* { dg-message "expected between 2 and 3, 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 } */
+/* { dg-message "expected between 2 and 3, 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 func3 (char *) __attribute__((nonnull_if_nonzero(1, 2, 3, 4))); /* 
{ dg-error "wrong number of arguments specified for 'nonnull_if_nonzero' 
attribute" } */
+/* { dg-message "expected between 2 and 3, found 4" "" { 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" } */
+extern void func6 (char *, int) __attribute__((nonnull_if_nonzero(1, 2, 3))); 
/* { dg-warning "nonnull_if_nonzero' attribute argument 3 value '3' exceeds the 
number of function parameters 2" } */
+
+extern void func7 (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" } */
+extern void func8 (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 *, int) __attribute__((nonnull_if_nonzero (1, 2, 
baz))); /* { dg-warning ".nonnull_if_nonzero. attribute argument 3 is invalid" 
} */
+/* { dg-error ".baz. undeclared" "undeclared argument" { target *-*-* } .-1 } 
*/
+
+extern void func10 (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 func11 (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 func12 (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 func13 (char *, char *) __attribute__((nonnull_if_nonzero(1, 2))); 
/* { dg-warning "'nonnull_if_nonzero' attribute argument 2 value '2' refers to 
parameter type 'char \\\*'" } */
 
-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 func14 (char *, int, float) __attribute__((nonnull_if_nonzero(1, 
2, 3))); /* { dg-warning "'nonnull_if_nonzero' attribute argument 3 value '3' 
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 func15 (char *, long, _Bool) __attribute__((nonnull_if_nonzero(1, 
2, 3))); /* { dg-warning "'nonnull_if_nonzero' attribute argument 3 value '3' 
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 \\\*'" } */
+extern void func17 (char *, int, char *) __attribute__((nonnull_if_nonzero(1, 
2, 3))); /* { dg-warning "'nonnull_if_nonzero' attribute argument 3 value '3' 
refers to parameter type 'char \\\*'" } */
 
 void
 foo (void)
@@ -38,3 +49,8 @@ void
 bar (void)
 {
 }
+
+void
+baz (void)
+{
+}
--- gcc/testsuite/gcc.dg/nonnull-12.c.jj        2025-06-03 13:26:28.546313344 
+0200
+++ gcc/testsuite/gcc.dg/nonnull-12.c   2025-06-03 13:45:20.207486991 +0200
@@ -0,0 +1,73 @@
+/* Test for the "nonnull_if_nonzero" function attribute.  */
+/* { dg-do compile } */
+/* { dg-options "-Wnonnull" } */
+
+#include <stddef.h>
+
+extern void func1 (char *, char *, int, int)
+  __attribute__((nonnull_if_nonzero (1, 3, 4), nonnull_if_nonzero (2, 3, 4)));
+
+extern void func2 (char *, char *, unsigned long, unsigned long)
+  __attribute__((nonnull_if_nonzero (1, 3, 4)));
+
+enum E { E0 = 0, E1 = 1, E2 = __INT_MAX__ };
+extern void func3 (char *, int, char *, enum E, int, enum E)
+  __attribute__((nonnull_if_nonzero (1, 4, 6), nonnull_if_nonzero (3, 2, 5)));
+
+extern void func4 (long, char *, char *, long, long, long)
+  __attribute__((nonnull_if_nonzero (2, 1, 5)))
+  __attribute__((nonnull_if_nonzero (3, 4, 6)));
+
+void
+foo (int i1, int i2, int i3, char *cp1, char *cp2, char *cp3)
+{
+  func1 (cp1, cp2, i1, i2);
+  func1 (cp1, cp2, 0, 0);
+  func1 (cp1, cp2, 42, 42);
+  func1 (NULL, NULL, 0, 0);
+  func1 (NULL, NULL, 0, 42);
+  func1 (NULL, NULL, 42, 0);
+  func1 (NULL, NULL, i1, i2);
+
+  func1 (NULL, cp2, 42, 42); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  func1 (cp1, NULL, 1, 1); /* { dg-warning "argument 2 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+
+  func2 (cp1, NULL, 17, 17);
+  func2 (NULL, cp2, 0, 0);
+  func2 (NULL, cp2, 0, 17);
+  func2 (NULL, cp2, 17, 0);
+  func2 (NULL, cp1, 2, 2); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+
+  func3 (NULL, i2, cp3, i3, i3, i2);
+  func3 (cp1, i2, NULL, i3, i3, i2);
+  func3 (NULL, i2, cp3, E0, i3, E0);
+  func3 (NULL, i2, cp3, E0, i3, E1);
+  func3 (NULL, i2, cp3, E1, i3, E0);
+  func3 (cp1, 0, NULL, E2, 0, E2);
+  func3 (cp1, 0, NULL, E2, 4, E2);
+  func3 (cp1, 4, NULL, E2, 0, E2);
+  func3 (NULL, i2, cp3, E2, i3, E2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  func3 (cp3, 5, NULL, i3, 1, i2); /* { dg-warning "argument 3 null where 
non-null expected because arguments 2 and 5 are nonzero" } */
+
+  func1 (i2 ? cp1 : NULL, cp2, i3, i3);
+  func1 (i2 ? NULL : cp1, cp2, i3, i3);
+  func1 (i2 ? (i3 ? cp1 : NULL) : cp2, cp3, i1, i1);
+  func1 (i1 ? cp1 : NULL, cp2, 0, 0);
+  func1 (i1 ? cp1 : NULL, cp2, 0, 4);
+  func1 (i1 ? cp1 : NULL, cp2, 4, 0);
+  func1 (i1 ? NULL : cp1, cp2, 0, 0);
+  func1 (i1 ? NULL : cp1, cp2, 0, 2);
+  func1 (i1 ? NULL : cp1, cp2, 3, 0);
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 0, 0);
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 0, 1);
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 2, 0);
+  func1 (i1 ? cp1 : NULL, cp2, 1, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 3 and 4 are nonzero" } */
+  func1 (i1 ? NULL : cp1, cp2, 2, 3); /* { dg-warning "argument 1 null where 
non-null expected because arguments 3 and 4 are nonzero" } */
+  func1 (i1 ? (i2 ? cp1 : NULL) : cp2, cp3, 3, 4); /* { dg-warning "argument 1 
null where non-null expected because arguments 3 and 4 are nonzero" } */
+
+  func4 (0, NULL, NULL, 0, 0, 0);
+  func4 (0, NULL, NULL, 0, 1, 2);
+  func4 (3, NULL, NULL, 4, 0, 0);
+  func4 (-1, NULL, cp1, 0, 42, 0); /* { dg-warning "argument 2 null where 
non-null expected because arguments 1 and 5 are nonzero" } */
+  func4 (0, cp1, NULL, 77, 0, 12); /* { dg-warning "argument 3 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+}
--- gcc/testsuite/gcc.dg/nonnull-13.c.jj        2025-06-03 13:47:31.404941652 
+0200
+++ gcc/testsuite/gcc.dg/nonnull-13.c   2025-06-03 14:45:14.548303521 +0200
@@ -0,0 +1,210 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wnonnull" } */
+
+#define N(x, y, z) __attribute__ ((nonnull_if_nonzero (x, y, z)))
+
+void N (1, 2, 3) f1_1 (void *, int, int);
+
+void N (1, 3, 4) f2_1 (void *, void *, int, int);
+void N (1, 3, 4) N (2, 3, 4) f2_1_2 (void *, void *, int, int);
+
+void N (1, 4, 6) N (3, 5, 7) f3_1_3 (void *, void *, void *, int, int, int, 
int);
+
+void N (1, 5, 6) N (2, 5, 6) N (4, 5, 6) g4_1_2_4 (void *, void *, void *, 
void *, long, long);
+void N (1, 5, 6) N (3, 5, 6) N (4, 5, 6) g4_1_3_4 (void *, void *, void *, 
void *, long, long);
+void N (2, 5, 6) N (3, 5, 6) N (4, 5, 6) g4_2_3_4 (void *, void *, void *, 
void *, long, long);
+
+void N (1, 17, 18) N (3, 17, 18) N (5, 17, 18) N (7, 17, 18) N (11, 17, 18) N 
(13, 17, 18)
+g16_1_3_5_7_11_13 (void *, void *, void *, void *,
+                  void *, void *, void *, void *,
+                  void *, void *, void *, void *,
+                  void *, void *, void *, void *, int, int);
+
+static void *null (void) { return 0; }
+
+void
+test (int t, long u, int v, long w)
+{
+  void *p0 = null ();
+  void *px = &px;
+
+  f1_1 (p0, 0, 0);
+  f1_1 (p0, 0, 4);
+  f1_1 (p0, 3, 0);
+  f1_1 (p0, t, v);
+  f1_1 (p0, t, 0);
+  f1_1 (p0, 0, v);
+  f1_1 (p0, 42, 1); /* { dg-warning "argument 1 null where non-null expected 
because arguments 2 and 3 are nonzero" } */
+  if (t && v)
+    f1_1 (p0, t, v); /* { dg-warning "argument 1 null where non-null expected 
because arguments 2 and 3 are nonzero" } */
+  f1_1 (px, 17, 17);
+
+  f2_1 (p0, px, 0, 0);
+  f2_1 (p0, px, 0, 3);
+  f2_1 (p0, px, 7, 0);
+  f2_1 (p0, px, t, v);
+  f2_1 (p0, px, t, 0);
+  f2_1 (p0, px, 0, v);
+  f2_1 (p0, px, 5, 3);  /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  if (t > 4 && v > 8)
+    f2_1 (p0, px, t, v);  /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  f2_1 (px, p0, 17, 17);
+  f2_1 (p0, p0, 0, 0);
+  f2_1 (p0, p0, 0, 4);
+  f2_1 (p0, p0, 2, 0);
+  if (t < 0 && v < -3)
+    f2_1 (p0, p0, t, v);  /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+
+  f2_1_2 (p0, p0, 0, 0);
+  f2_1_2 (p0, p0, 0, 1);
+  f2_1_2 (p0, p0, 2, 0);
+  f2_1_2 (p0, p0, t, v);
+  f2_1_2 (p0, p0, t, 0);
+  f2_1_2 (p0, p0, 0, v);
+  f2_1_2 (p0, px, 1, 2); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  if (t > 8 && v >= 16)
+    f2_1_2 (p0, px, t, v); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  f2_1_2 (px, p0, -3, -4); /* { dg-warning "argument 2 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  if (t < -2 && v >= 32)
+    f2_1_2 (px, p0, t, v); /* { dg-warning "argument 2 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  f2_1_2 (p0, p0, 8, 165); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  /* { dg-warning "argument 2 null where non-null expected because arguments 3 
and 4 are nonzero" "argument 2" { target *-*-* } .-1 } */
+  if (t > 7 && v < -2)
+    f2_1_2 (p0, p0, t, v); /* { dg-warning "argument 1 null where non-null 
expected because arguments 3 and 4 are nonzero" } */
+  /* { dg-warning "argument 2 null where non-null expected because arguments 3 
and 4 are nonzero" "argument 2" { target *-*-* } .-1 } */
+
+  f3_1_3 (p0, p0, p0, 0, 0, 0, 0);
+  f3_1_3 (p0, p0, p0, 0, 5, 4, 0);
+  f3_1_3 (p0, p0, p0, 3, 0, 0, 6);
+  f3_1_3 (p0, p0, px, 0, 6, 0, 6);
+  f3_1_3 (p0, p0, px, 0, 6, 4, 6);
+  f3_1_3 (p0, p0, px, 3, 6, 0, 6);
+  f3_1_3 (px, p0, p0, 2, 0, 2, 0);
+  f3_1_3 (px, p0, p0, 2, 0, 2, 4);
+  f3_1_3 (px, p0, p0, 2, 6, 2, 0);
+  f3_1_3 (p0, p0, p0, t, t, v, v);
+  f3_1_3 (p0, p0, px, t, 6, v, 7);
+  f3_1_3 (px, p0, p0, 2, t, 3, v);
+  f3_1_3 (p0, px, px, 8, 2, 3, 5); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  if (t > 9 && v < -19)
+    f3_1_3 (p0, px, px, t, 3, v, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  f3_1_3 (px, p0, px, 9, 10, 1, 2);
+  if (t > 11 && v > 3)
+    f3_1_3 (px, p0, px, t, t, v, v);
+  f3_1_3 (px, px, p0, 10, 11, 2, 3); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 7 are nonzero" } */
+  if (t < -5 && v > 2)
+    f3_1_3 (px, px, p0, 0, t, 2, v); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 7 are nonzero" } */
+  f3_1_3 (p0, p0, px, 11, 12, 1, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  if (t > 26 && v > 88)
+    f3_1_3 (p0, p0, px, t, 3, v, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  f3_1_3 (px, p0, p0, 12, 13, 1, 2); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 7 are nonzero" } */
+  if (t > 31 && v < -1)
+    f3_1_3 (px, p0, p0, 12, t, 2, v); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 7 are nonzero" } */
+  f3_1_3 (p0, p0, p0, 13, 14, 1, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 4 and 6 are nonzero" } */
+  /* { dg-warning "argument 3 null where non-null expected because arguments 5 
and 7 are nonzero" "argument 3" { target *-*-* } .-1 } */
+  if (t > 28 && v > 42)
+    f3_1_3 (p0, p0, p0, t, t + 1, v, v + 1); /* { dg-warning "argument 1 null 
where non-null expected because arguments 4 and 6 are nonzero" } */
+  /* { dg-warning "argument 3 null where non-null expected because arguments 5 
and 7 are nonzero" "argument 3" { target *-*-* } .-1 } */
+
+  g4_1_2_4 (p0, px, px, px, u, w);
+  g4_1_2_4 (px, p0, px, px, u, w);
+  g4_1_2_4 (px, px, p0, px, u, w);
+  g4_1_2_4 (px, px, px, p0, u, w);
+  g4_1_2_4 (p0, px, px, px, 0, 0);
+  g4_1_2_4 (p0, px, px, px, 0, 2);
+  g4_1_2_4 (p0, px, px, px, 1, 0);
+  g4_1_2_4 (px, p0, px, px, 0, 0);
+  g4_1_2_4 (px, p0, px, px, 0, 3);
+  g4_1_2_4 (px, p0, px, px, 4, 0);
+  g4_1_2_4 (px, px, p0, px, 0, 0);
+  g4_1_2_4 (px, px, p0, px, 0, 5);
+  g4_1_2_4 (px, px, p0, px, 6, 0);
+  g4_1_2_4 (px, px, px, p0, 0, 0);
+  g4_1_2_4 (px, px, px, p0, 0, 7);
+  g4_1_2_4 (px, px, px, p0, 8, 0);
+  g4_1_2_4 (p0, px, px, px, 15, 2); /* { dg-warning "argument 1 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u && w)
+    g4_1_2_4 (p0, px, px, px, u, w); /* { dg-warning "argument 1 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_1_2_4 (px, p0, px, px, 16, 2); /* { dg-warning "argument 2 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 2 && w > 3)
+    g4_1_2_4 (px, p0, px, px, u, w); /* { dg-warning "argument 2 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_1_2_4 (px, px, p0, px, 17, 8);
+  if (u > 3 && w < -2)
+    g4_1_2_4 (px, px, p0, px, u, w);
+  g4_1_2_4 (px, px, px, p0, 18, 3); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if ((u < -2 || u > 10) && (w < -4 || w > 42))
+    g4_1_2_4 (px, px, px, p0, u, w); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+
+  g4_1_3_4 (p0, px, px, px, u, u);
+  g4_1_3_4 (px, p0, px, px, u, u);
+  g4_1_3_4 (px, px, p0, px, u, u);
+  g4_1_3_4 (px, px, px, p0, u, u);
+  g4_1_3_4 (p0, px, px, px, 0, 0);
+  g4_1_3_4 (p0, px, px, px, 0, 1);
+  g4_1_3_4 (p0, px, px, px, 2, 0);
+  g4_1_3_4 (px, p0, px, px, 0, 0);
+  g4_1_3_4 (px, p0, px, px, 0, 3);
+  g4_1_3_4 (px, p0, px, px, 4, 0);
+  g4_1_3_4 (px, px, p0, px, 0, 0);
+  g4_1_3_4 (px, px, p0, px, 0, 5);
+  g4_1_3_4 (px, px, p0, px, 6, 0);
+  g4_1_3_4 (px, px, px, p0, 0, 0);
+  g4_1_3_4 (px, px, px, p0, 0, 7);
+  g4_1_3_4 (px, px, px, p0, 8, 0);
+  g4_1_3_4 (p0, px, px, px, 20, 32); /* { dg-warning "argument 1 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 4 && w > 2)
+    g4_1_3_4 (p0, px, px, px, u, w); /* { dg-warning "argument 1 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_1_3_4 (px, p0, px, px, 21, 4);
+  if ((u > 6 || u < -24) && (w > 8 || w < -5))
+    g4_1_3_4 (px, p0, px, px, u, w);
+  g4_1_3_4 (px, px, p0, px, 22, 4); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 9 && w > 13)
+    g4_1_3_4 (px, px, p0, px, u - 3, w - 8); /* { dg-warning "argument 3 null 
where non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_1_3_4 (px, px, px, p0, 23, 8); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 10 && w > 12)
+    g4_1_3_4 (px, px, px, p0, u, w); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+
+  g4_2_3_4 (p0, px, px, px, u, u);
+  g4_2_3_4 (px, p0, px, px, u, u);
+  g4_2_3_4 (px, px, p0, px, u, u);
+  g4_2_3_4 (px, px, px, p0, u, u);
+  g4_2_3_4 (p0, px, px, px, 0, 0);
+  g4_2_3_4 (p0, px, px, px, 0, 1);
+  g4_2_3_4 (p0, px, px, px, 2, 0);
+  g4_2_3_4 (px, p0, px, px, 0, 0);
+  g4_2_3_4 (px, p0, px, px, 0, 3);
+  g4_2_3_4 (px, p0, px, px, 4, 0);
+  g4_2_3_4 (px, px, p0, px, 0, 0);
+  g4_2_3_4 (px, px, p0, px, 0, 5);
+  g4_2_3_4 (px, px, p0, px, 6, 0);
+  g4_2_3_4 (px, px, px, p0, 0, 0);
+  g4_2_3_4 (px, px, px, p0, 0, 7);
+  g4_2_3_4 (px, px, px, p0, 8, 0);
+  g4_2_3_4 (p0, px, px, px, 1, 2);
+  if (u > 12 && w > 16)
+    g4_2_3_4 (p0, px, px, px, u, w);
+  g4_2_3_4 (px, p0, px, px, 2, 3); /* { dg-warning "argument 2 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 17 && w > 19)
+    g4_2_3_4 (px, p0, px, px, u - 3, w - 2); /* { dg-warning "argument 2 null 
where non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_2_3_4 (px, px, p0, px, 3, 8); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 24 && w > 22)
+    g4_2_3_4 (px, px, p0, px, u, w); /* { dg-warning "argument 3 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  g4_2_3_4 (px, px, px, p0, 4, 2); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are nonzero" } */
+  if (u > 42 && w > 48)
+    g4_2_3_4 (px, px, px, p0, u, w); /* { dg-warning "argument 4 null where 
non-null expected because arguments 5 and 6 are 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, 18);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, t, v);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, 0, 0);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, 0, 4);
+  g16_1_3_5_7_11_13 (p0, p0, p0, p0, p0, p0, p0, p0,
+                    p0, p0, p0, p0, p0, p0, p0, p0, 3, 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, 1); /* { dg-warning "argument 13 null where non-null expected 
because arguments 17 and 18 are nonzero" } */
+  if (t > 122 && v > 18)
+    g16_1_3_5_7_11_13 (px, p0, px, p0, px, p0, px, p0, p0, p0, px, p0, p0, p0, 
p0, p0, t, v); /* { dg-warning "argument 13 null where non-null expected 
because arguments 17 and 18 are nonzero" } */
+}
--- gcc/testsuite/gcc.dg/nonnull-14.c.jj        2025-06-03 14:32:16.368426685 
+0200
+++ gcc/testsuite/gcc.dg/nonnull-14.c   2025-06-03 14:42:31.165427899 +0200
@@ -0,0 +1,23 @@
+/* Test for the "nonnull" function attribute on builtins.  Use the
+   "__builtin_" style below so we don't need prototypes.  */
+/* { dg-do compile } */
+/* { dg-options "-Wnonnull" } */
+
+#include <stddef.h>
+
+void
+foo (void *p, char *s)
+{
+  __builtin_fwrite (s, 0, 0, NULL);  /* { dg-warning "null" "null pointer 
check" } */
+  __builtin_fwrite (s, 0, 2, NULL);  /* { dg-warning "null" "null pointer 
check" } */
+  __builtin_fwrite (s, 1, 0, NULL);  /* { dg-warning "null" "null pointer 
check" } */
+  __builtin_fwrite (NULL, 16, 0, p);
+  __builtin_fwrite (NULL, 0, 12, p);
+  __builtin_fwrite (NULL, 2, 3, p);  /* { dg-warning "null" "null pointer 
check" } */
+  __builtin_fwrite_unlocked (s, 0, 0, NULL);  /* { dg-warning "null" "null 
pointer check" } */
+  __builtin_fwrite_unlocked (s, 0, 2, NULL);  /* { dg-warning "null" "null 
pointer check" } */
+  __builtin_fwrite_unlocked (s, 1, 0, NULL);  /* { dg-warning "null" "null 
pointer check" } */
+  __builtin_fwrite_unlocked (NULL, 16, 0, p);
+  __builtin_fwrite_unlocked (NULL, 0, 12, p);
+  __builtin_fwrite_unlocked (NULL, 2, 3, p);  /* { dg-warning "null" "null 
pointer check" } */
+}
--- gcc/testsuite/c-c++-common/ubsan/nonnull-8.c.jj     2025-06-03 
15:28:12.571656897 +0200
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-8.c        2025-06-03 
15:36:08.285443385 +0200
@@ -0,0 +1,32 @@
+/* { dg-do run } */
+/* { dg-options "-fsanitize=undefined -fno-sanitize-recover=undefined" } */
+
+__attribute__((noipa, nonnull_if_nonzero (1, 4, 7)))
+__attribute__((nonnull (3), nonnull_if_nonzero (5, 2, 6))) void
+foo (void *a, unsigned long b, void *c, int d, void *e, unsigned long f, int g)
+{
+  (void) a;
+  (void) b;
+  (void) c;
+  (void) d;
+  (void) e;
+  (void) f;
+  (void) g;
+}
+
+__attribute__((noipa))
+void
+bar (void *a, unsigned long b, void *c, int d, void *e, unsigned long f, int g)
+{
+  foo (a, b, c, d, e, f, g);
+}
+
+int
+main ()
+{
+  char x;
+  bar (&x, 42, &x, 1, &x, 2, 3);
+  bar (0, 0, &x, 0, 0, 0, 0);
+  bar (0, 5, &x, 4, 0, 0, 0);
+  bar (0, 0, &x, 0, 0, 6, 7);
+}
--- gcc/testsuite/c-c++-common/ubsan/nonnull-9.c.jj     2025-06-03 
15:28:15.330620875 +0200
+++ gcc/testsuite/c-c++-common/ubsan/nonnull-9.c        2025-06-03 
15:34:45.031530823 +0200
@@ -0,0 +1,41 @@
+/* { dg-do run } */
+/* { dg-options "-fsanitize=nonnull-attribute" } */
+
+__attribute__((noipa, nonnull_if_nonzero (1, 4, 7)))
+__attribute__((nonnull (3), nonnull_if_nonzero (5, 2, 6))) void
+foo (void *a, unsigned long b, void *c, int d, void *e, unsigned long f, int g)
+{
+  (void) a;
+  (void) b;
+  (void) c;
+  (void) d;
+  (void) e;
+  (void) f;
+  (void) g;
+}
+
+__attribute__((noipa))
+void
+bar (void *a, unsigned long b, void *c, int d, void *e, unsigned long f, int g)
+{
+  foo (a, b, c, d, e, f, g);
+}
+
+int
+main ()
+{
+  char x;
+  bar (&x, 42, 0, 1, &x, 17, 18);
+  bar (0, 25, &x, 7, &x, 0, 8);
+  bar (&x, -82, &x, 68, 0, 9, 0);
+  foo (&x, 42, 0, 1, &x, 17, 18);
+  foo (0, 25, &x, 7, &x, 0, 8);
+  foo (&x, -82, &x, 68, 0, 9, 0);
+}
+
+/* { dg-output "\.c:21:\[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:21:\[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:21:\[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:31:\[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:32:\[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:33:\[0-9]*:\[^\n\r]*null pointer passed as 
argument 5, which is declared to never be null" } */

        Jakub

Reply via email to