https://gcc.gnu.org/g:19fe55c4801de50deee03b333e94d007aae222e3
commit r15-5750-g19fe55c4801de50deee03b333e94d007aae222e3 Author: Jakub Jelinek <ja...@redhat.com> Date: Thu Nov 28 11:48:33 2024 +0100 Add support for nonnull_if_nonzero attribute [PR117023] 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. I'd then follow with the builtins.def changes (and eventually glibc etc. would need to be adjusted too). 2024-11-28 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. Diff: --- gcc/builtins.cc | 18 +++ gcc/c-family/c-attribs.cc | 29 ++++- gcc/c-family/c-common.cc | 54 +++++++-- gcc/doc/extend.texi | 35 +++++- gcc/gimple.cc | 47 +++++++- gcc/gimple.h | 2 +- gcc/testsuite/c-c++-common/ubsan/nonnull-6.c | 28 +++++ gcc/testsuite/c-c++-common/ubsan/nonnull-7.c | 39 +++++++ gcc/testsuite/gcc.dg/nonnull-10.c | 162 +++++++++++++++++++++++++++ gcc/testsuite/gcc.dg/nonnull-8.c | 57 ++++++++++ gcc/testsuite/gcc.dg/nonnull-9.c | 40 +++++++ gcc/tree-ssa-ccp.cc | 68 ++++++++++- gcc/tree.cc | 2 +- gcc/ubsan.cc | 22 +++- 14 files changed, 577 insertions(+), 26 deletions(-) diff --git a/gcc/builtins.cc b/gcc/builtins.cc index d925074c5475..fd7acdfc915b 100644 --- a/gcc/builtins.cc +++ b/gcc/builtins.cc @@ -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; } diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index 5a71749d2f9c..5b64805f97de 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -138,6 +138,8 @@ static tree handle_vector_size_attribute (tree *, tree, tree, int, 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 *); @@ -487,6 +489,8 @@ const struct attribute_spec c_common_gnu_attributes[] = 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, @@ -5002,7 +5006,7 @@ handle_nonnull_attribute (tree *node, tree name, /* 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)) @@ -5018,6 +5022,29 @@ handle_nonnull_attribute (tree *node, tree name, 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 */ static tree diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index 721407157bc3..a8f25d6cb944 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -5721,6 +5721,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; }; @@ -5759,23 +5761,19 @@ check_function_nonnull (nonnull_arg_ctx &ctx, int nargs, tree *argarray) } 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. */ @@ -5794,6 +5792,28 @@ check_function_nonnull (nonnull_arg_ctx &ctx, int nargs, tree *argarray) 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; } @@ -5975,13 +5995,23 @@ check_nonnull_arg (void *ctx, tree param, unsigned HOST_WIDE_INT param_num) } 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) @@ -6227,7 +6257,7 @@ check_function_arguments (location_t loc, const_tree fndecl, const_tree fntype, 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); } diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 2fc513efdb58..8497aa8603f2 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -2754,9 +2754,10 @@ object size, for example in functions that call @code{__builtin_object_size}. 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 @@ -3788,6 +3789,34 @@ my_memcpy (void *dest, const void *src, size_t len) __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}. diff --git a/gcc/gimple.cc b/gcc/gimple.cc index c6d0991ded90..477315cb1b86 100644 --- a/gcc/gimple.cc +++ b/gcc/gimple.cc @@ -3142,10 +3142,17 @@ infer_nonnull_range_by_dereference (gimple *stmt, tree op) } /* 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 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) +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 @@ -3162,9 +3169,10 @@ infer_nonnull_range_by_attribute (gimple *stmt, tree op) 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. */ @@ -3191,6 +3199,37 @@ infer_nonnull_range_by_attribute (gimple *stmt, tree op) } } } + + 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 diff --git a/gcc/gimple.h b/gcc/gimple.h index b6967e63de23..039ed66eab5d 100644 --- a/gcc/gimple.h +++ b/gcc/gimple.h @@ -1662,7 +1662,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); diff --git a/gcc/testsuite/c-c++-common/ubsan/nonnull-6.c b/gcc/testsuite/c-c++-common/ubsan/nonnull-6.c new file mode 100644 index 000000000000..8e072feed89a --- /dev/null +++ b/gcc/testsuite/c-c++-common/ubsan/nonnull-6.c @@ -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); +} diff --git a/gcc/testsuite/c-c++-common/ubsan/nonnull-7.c b/gcc/testsuite/c-c++-common/ubsan/nonnull-7.c new file mode 100644 index 000000000000..a8be2a7a376e --- /dev/null +++ b/gcc/testsuite/c-c++-common/ubsan/nonnull-7.c @@ -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" } */ diff --git a/gcc/testsuite/gcc.dg/nonnull-10.c b/gcc/testsuite/gcc.dg/nonnull-10.c new file mode 100644 index 000000000000..447ad301275a --- /dev/null +++ b/gcc/testsuite/gcc.dg/nonnull-10.c @@ -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" } */ +} diff --git a/gcc/testsuite/gcc.dg/nonnull-8.c b/gcc/testsuite/gcc.dg/nonnull-8.c new file mode 100644 index 000000000000..64e36f9d96ee --- /dev/null +++ b/gcc/testsuite/gcc.dg/nonnull-8.c @@ -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" } */ +} diff --git a/gcc/testsuite/gcc.dg/nonnull-9.c b/gcc/testsuite/gcc.dg/nonnull-9.c new file mode 100644 index 000000000000..c0f95ca66631 --- /dev/null +++ b/gcc/testsuite/gcc.dg/nonnull-9.c @@ -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) +{ +} diff --git a/gcc/tree-ssa-ccp.cc b/gcc/tree-ssa-ccp.cc index 21c5440c9a25..0c08ed62cda2 100644 --- a/gcc/tree-ssa-ccp.cc +++ b/gcc/tree-ssa-ccp.cc @@ -154,6 +154,7 @@ along with GCC; see the file COPYING3. If not see #include "ipa-cp.h" #include "ipa-prop.h" #include "internal-fn.h" +#include "gimple-range.h" /* Possible lattice values. */ typedef enum @@ -4546,6 +4547,7 @@ unsigned int pass_post_ipa_warn::execute (function *fun) { basic_block bb; + gimple_ranger *ranger = NULL; FOR_EACH_BB_FN (bb, fun) { @@ -4557,14 +4559,15 @@ pass_post_ipa_warn::execute (function *fun) 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) @@ -4612,8 +4615,67 @@ pass_post_ipa_warn::execute (function *fun) 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; } diff --git a/gcc/tree.cc b/gcc/tree.cc index 3ef1b6b483b3..74d13a8d5832 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -14785,7 +14785,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); diff --git a/gcc/ubsan.cc b/gcc/ubsan.cc index b858795aaf72..1a8faed6cc29 100644 --- a/gcc/ubsan.cc +++ b/gcc/ubsan.cc @@ -2046,8 +2046,9 @@ instrument_nonnull_arg (gimple_stmt_iterator *gsi) 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)) @@ -2057,6 +2058,13 @@ instrument_nonnull_arg (gimple_stmt_iterator *gsi) 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, @@ -2068,6 +2076,18 @@ instrument_nonnull_arg (gimple_stmt_iterator *gsi) 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