Hi! If we consider -fno-delete-null-pointer-checks as a way to support e.g. AVR and other targets which can validly place objects at NULL rather than a way to workaround UBs in code, I believe the following testcase must pass if there is e.g. char a[32]; // And this object ends up at address 0 void bar (void); int main () { foo (&a[3]); baz (&a[6]); } but fails right now. As mentioned in the PR, in GCC 8 we used to do: else if (code == POINTER_PLUS_EXPR) { /* For pointer types, we are really only interested in asserting whether the expression evaluates to non-NULL. */ if (range_is_nonnull (&vr0) || range_is_nonnull (&vr1)) set_value_range_to_nonnull (vr, expr_type); and that triggered pretty much never, as range_is_nonnull requires that the offset is ~[0, 0] exactly, e.g. if it is a constant, it is never that way, but now we do: if (!range_includes_zero_p (&vr0) || !range_includes_zero_p (&vr1)) which is e.g. always if the offset is constant non-zero.
I hope we can still say that pointer wrapping even with -fno-delete-null-pointer-checks is UB, so this patch differentiates between positive offsets (in ssizetype), negative offsets (in ssizetype) and zero offsets and handles both the same for both ptr p+ offset and &MEM_REF[ptr, offset] If offset is 0 and ptr is ~[0, 0], then the result is ~[0, 0] as before. If offset is positive in ssizetype, then even for VARYING ptr the result is ~[0, 0] pointer. If the offset is (or maybe could be) negative in ssizetype, then for -fno-delete-null-pointer-checks the result is VARYING, as we could go from a non-NULL pointer back to NULL on those targets; for -fdelete-null-pointer-checks we do what we've done before, i.e. ~[0, 0]. Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? 2018-12-06 Jakub Jelinek <ja...@redhat.com> PR c/88367 * tree-vrp.c (extract_range_from_binary_expr): For POINTER_PLUS_EXPR with -fno-delete-null-pointer-checks, set_nonnull only if the pointer is non-NULL and offset is known to have most significant bit clear. * vr-values.c (vr_values::vrp_stmt_computes_nonzero): For ADDR_EXPR of MEM_EXPR, return true if the MEM_EXPR has non-zero offset with most significant bit clear. If offset does have most significant bit set and -fno-delete-null-pointer-checks, don't return true even if the base pointer is non-NULL. * gcc.dg/tree-ssa/pr88367.c: New test. --- gcc/tree-vrp.c.jj 2018-12-04 13:00:02.408635579 +0100 +++ gcc/tree-vrp.c 2018-12-05 19:07:36.187567781 +0100 @@ -1673,9 +1673,25 @@ extract_range_from_binary_expr (value_ra else if (code == POINTER_PLUS_EXPR) { /* For pointer types, we are really only interested in asserting - whether the expression evaluates to non-NULL. */ - if (!range_includes_zero_p (&vr0) - || !range_includes_zero_p (&vr1)) + whether the expression evaluates to non-NULL. + With -fno-delete-null-pointer-checks we need to be more + conservative. As some object might reside at address 0, + then some offset could be added to it and the same offset + subtracted again and the result would be NULL. + E.g. + static int a[12]; where &a[0] is NULL and + ptr = &a[6]; + ptr -= 6; + ptr will be NULL here, even when there is POINTER_PLUS_EXPR + where the first range doesn't include zero and the second one + doesn't either. As the second operand is sizetype (unsigned), + consider all ranges where the MSB could be set as possible + subtractions where the result might be NULL. */ + if ((!range_includes_zero_p (&vr0) + || !range_includes_zero_p (&vr1)) + && (flag_delete_null_pointer_checks + || (range_int_cst_p (&vr1) + && !tree_int_cst_sign_bit (vr1.max ())))) vr->set_nonnull (expr_type); else if (range_is_null (&vr0) && range_is_null (&vr1)) vr->set_null (expr_type); --- gcc/vr-values.c.jj 2018-11-29 08:41:33.152749436 +0100 +++ gcc/vr-values.c 2018-12-05 19:37:56.222582823 +0100 @@ -303,8 +303,17 @@ vr_values::vrp_stmt_computes_nonzero (gi && TREE_CODE (base) == MEM_REF && TREE_CODE (TREE_OPERAND (base, 0)) == SSA_NAME) { - value_range *vr = get_value_range (TREE_OPERAND (base, 0)); - if (!range_includes_zero_p (vr)) + if (integer_zerop (TREE_OPERAND (base, 1)) + || flag_delete_null_pointer_checks) + { + value_range *vr = get_value_range (TREE_OPERAND (base, 0)); + if (!range_includes_zero_p (vr)) + return true; + } + /* If MEM_REF has a "positive" offset, consider it non-NULL + always. */ + if (integer_nonzerop (TREE_OPERAND (base, 1)) + && !tree_int_cst_sign_bit (TREE_OPERAND (base, 1))) return true; } } --- gcc/testsuite/gcc.dg/tree-ssa/pr88367.c.jj 2018-12-05 19:24:36.386727781 +0100 +++ gcc/testsuite/gcc.dg/tree-ssa/pr88367.c 2018-12-05 19:40:43.228839763 +0100 @@ -0,0 +1,31 @@ +/* PR c/88367 */ +/* { dg-do compile } */ +/* { dg-options "-fno-delete-null-pointer-checks -O2 -fdump-tree-optimized" } */ +/* { dg-final { scan-tree-dump-not "link_error \\(\\);" "optimized" } } */ +/* { dg-final { scan-tree-dump-times "bar \\(\\);" 2 "optimized" } } */ + +void bar (void); +void link_error (void); + +void +foo (char *p) +{ + if (!p) + return; + p += 3; + if (!p) + link_error (); + p -= 6; + if (!p) + bar (); +} + +void +baz (char *p) +{ + if (!p) + return; + p -= 6; + if (!p) + bar (); +} Jakub