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