Index: gcc/tree-vrp.c =================================================================== --- gcc/tree-vrp.c (revision 225868) +++ gcc/tree-vrp.c (working copy) @@ -4936,11 +4936,11 @@ infer_value_range (gimple stmt, tree op, break; if (e == NULL) return false; } - if (infer_nonnull_range (stmt, op, true, true)) + if (infer_nonnull_range (stmt, op)) { *val_p = build_int_cst (TREE_TYPE (op), 0); *comp_code_p = NE_EXPR; return true; } Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 225868) +++ gcc/doc/invoke.texi (working copy) @@ -258,10 +258,11 @@ Objective-C and Objective-C++ Dialects}. -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol -Wignored-qualifiers -Wincompatible-pointer-types @gol -Wimplicit -Wimplicit-function-declaration -Wimplicit-int @gol -Winit-self -Winline -Wno-int-conversion @gol -Wno-int-to-pointer-cast -Wno-invalid-offsetof @gol +-Wnull-dereference @gol -Winvalid-pch -Wlarger-than=@var{len} -Wunsafe-loop-optimizations @gol -Wlogical-op -Wlogical-not-parentheses -Wlong-long @gol -Wmain -Wmaybe-uninitialized -Wmemset-transposed-args @gol -Wmisleading-indentation -Wmissing-braces @gol -Wmissing-field-initializers -Wmissing-include-dirs @gol @@ -4130,10 +4133,20 @@ All the above @option{-Wunused} options In order to get a warning about an unused function parameter, you must either specify @option{-Wextra -Wunused} (note that @option{-Wall} implies @option{-Wunused}), or separately specify @option{-Wunused-parameter}. +@item -Wnull-dereference +@opindex Wnull-dereference +@opindex Wno-null-dereference +Warn if the compiler detects paths which trigger erroneous or +undefined behaviour due to dereferencing a NULL pointer. This option +is only active when @option{-fdelete-null-pointer-checks} is active, +which is enabled by optimizations in most targets. The precision of +the warnings depends on the optimization options used. This option is +enabled by @option{-Wall}. + @item -Wuninitialized @opindex Wuninitialized @opindex Wno-uninitialized Warn if an automatic variable is used without first being initialized or if a variable may be clobbered by a @code{setjmp} call. In C++, Index: gcc/testsuite/gcc.dg/tree-ssa/isolate-2.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/isolate-2.c (revision 225868) +++ gcc/testsuite/gcc.dg/tree-ssa/isolate-2.c (working copy) @@ -33,10 +33,10 @@ bar (void) returns non-null attribute to isolate a path where NULL flows into a return statement. We test this twice, once where the NULL flows from a PHI, the second with an explicit return 0 in the IL. We also verify that after isolation phi-cprop simplifies the - return statement so that it returns &z directly. + return statement so that it returns &z directly. */ /* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "isolate-paths"} } */ /* { dg-final { scan-tree-dump-times "return &z;" 1 "phicprop1"} } */ Index: gcc/testsuite/gcc.dg/tree-ssa/isolate-4.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/isolate-4.c (revision 225868) +++ gcc/testsuite/gcc.dg/tree-ssa/isolate-4.c (working copy) @@ -22,10 +22,10 @@ bar (void) /* We testing that the path isolation code can take advantage of the returns non-null attribute to isolate a path where NULL flows into a return statement. We also verify that after isolation phi-cprop simplifies the - return statement so that it returns &z directly. + return statement so that it returns &z directly. */ /* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "isolate-paths"} } */ /* { dg-final { scan-tree-dump-times "foo .&z.;" 1 "phicprop1"} } */ Index: gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c (revision 0) +++ gcc/testsuite/gcc.dg/tree-ssa/wnull-dereference.c (revision 0) @@ -0,0 +1,80 @@ +/* { dg-do compile } */ +/* PR c/16531 */ +/* { dg-options "-O2 -fdelete-null-pointer-checks -Wnull-dereference" } */ +/* { dg-skip-if "" keeps_null_pointer_checks } */ + +#ifndef __cplusplus +#define NULL (void *)0 +#else +#define NULL nullptr +#endif + +struct t +{ + int bar; +}; + +struct t2 +{ + struct t *s; +}; + +void test1 () +{ + struct t *s = NULL; + s->bar = 1; /* { dg-warning "null" } */ +} + +void test2 (struct t *s) +{ + if (s == NULL && s->bar > 2) /* { dg-warning "null" } */ + return; + + s->bar = 3; +} + +void test3 (struct t *s) +{ + if (s != NULL || s->bar > 2) /* { dg-warning "null" } */ + return; + + s->bar = 3; /* { dg-warning "null" } */ +} + +void test4 (struct t *s) +{ + if (s != NULL && s->bar > 2) /* { dg-bogus "null" } */ + return; +} + +void test5 (struct t *s) +{ + if (s == NULL || s->bar > 2) /* { dg-bogus "null" } */ + return; + +} + +void test6 (struct t2 *s) +{ + if (s->s == 0 && s->s->bar == 0) /* { dg-warning "null" "null" { xfail *-*-* } } */ + return; +} + +int test7 (struct t *s) +{ + s = 0; + return s->bar; /* { dg-warning "null" } */ +} + +int test8 () +{ + return ((struct t *)0)->bar; /* { dg-warning "null" } */ +} + +void test9 (struct t **s) +{ + if (s == 0) + *s = 0; /* { dg-warning "null" } */ +} + + Index: gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c (revision 225868) +++ gcc/testsuite/gcc.dg/tree-ssa/isolate-1.c (working copy) @@ -1,8 +1,7 @@ - /* { dg-do compile } */ -/* { dg-options "-O2 -fdump-tree-isolate-paths -fdelete-null-pointer-checks" } */ +/* { dg-options "-O2 -fdump-tree-isolate-paths -fdelete-null-pointer-checks -Wnull-dereference" } */ /* { dg-skip-if "" keeps_null_pointer_checks } */ struct demangle_component { @@ -37,12 +36,12 @@ d_make_empty (struct d_info *di) struct demangle_component * d_type (struct d_info *di) { struct demangle_component *ret; ret = d_make_empty (di); - ret->type = 42; - ret->zzz = -1; + ret->type = 42; /* { dg-warning "null pointer dereference" } */ + ret->zzz = -1; /* { dg-warning "null pointer dereference" } */ return ret; } /* We're testing three aspects of isolation here. First that isolation occurs, second that if we have two null dereferences in a block that @@ -51,10 +50,5 @@ d_type (struct d_info *di) and finally that we set the RHS of the store to zero. */ /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "isolate-paths"} } */ /* { dg-final { scan-tree-dump-times "->type = 42" 1 "isolate-paths"} } */ /* { dg-final { scan-tree-dump-times "->type ={v} 0" 1 "isolate-paths"} } */ /* { dg-final { scan-tree-dump-times "->zzz" 1 "isolate-paths"} } */ - - - - - Index: gcc/testsuite/gcc.dg/tree-ssa/isolate-3.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/isolate-3.c (revision 225868) +++ gcc/testsuite/gcc.dg/tree-ssa/isolate-3.c (working copy) @@ -1,7 +1,7 @@ /* { dg-do compile } */ -/* { dg-options "-O2 -fdump-tree-isolate-paths -fdelete-null-pointer-checks" } */ +/* { dg-options "-O2 -fdump-tree-isolate-paths -fdelete-null-pointer-checks -Wnull-dereference" } */ /* { dg-skip-if "" keeps_null_pointer_checks } */ typedef __SIZE_TYPE__ size_t; extern void *memset (void *__s, int __c, size_t __n) @@ -27,11 +27,11 @@ typedef struct VEC_rtx_gc static __inline__ void VEC_rtx_gc_safe_grow (VEC_rtx_gc ** vec_, int size_, const char *file_, unsigned line_, const char *function_) { - ((*vec_) ? &(*vec_)->base : 0)->num = size_; + ((*vec_) ? &(*vec_)->base : 0)->num = size_; /* { dg-warning "null pointer dereference" } */ } static __inline__ void VEC_rtx_gc_safe_grow_cleared (VEC_rtx_gc ** vec_, int size_, const char *file_, unsigned line_, Index: gcc/testsuite/gcc.dg/tree-ssa/isolate-5.c =================================================================== --- gcc/testsuite/gcc.dg/tree-ssa/isolate-5.c (revision 225868) +++ gcc/testsuite/gcc.dg/tree-ssa/isolate-5.c (working copy) @@ -1,7 +1,7 @@ /* { dg-do compile } */ -/* { dg-options "-O2 -fdelete-null-pointer-checks -fdump-tree-isolate-paths -fdump-tree-optimized" } */ +/* { dg-options "-O2 -fdelete-null-pointer-checks -fdump-tree-isolate-paths -fdump-tree-optimized -Wnull-dereference" } */ /* { dg-skip-if "" keeps_null_pointer_checks } */ struct demangle_component { @@ -34,12 +34,12 @@ d_make_empty (struct d_info *di) struct demangle_component * d_type (struct d_info *di) { struct demangle_component *ret; ret = d_make_empty (di); - foo (ret->type); - bar (ret->zzz); + foo (ret->type); /* { dg-warning "null pointer dereference" } */ + bar (ret->zzz); /* { dg-warning "null pointer dereference" } */ return ret; } /* We're testing two aspects of isolation here. First that isolation occurs, second that if we have two null dereferences in a block that Index: gcc/testsuite/c-c++-common/wnonnull-1.c =================================================================== --- gcc/testsuite/c-c++-common/wnonnull-1.c (revision 0) +++ gcc/testsuite/c-c++-common/wnonnull-1.c (revision 0) @@ -0,0 +1,42 @@ +/* { dg-do compile } */ +/* { dg-options "-Wnonnull" } */ + + +extern void foo(void *) __attribute__ ((__nonnull__ (1))); + +int z; +int y; + +void +com (int a) +{ + foo (a == 42 ? &z : (void *) 0); /* { dg-warning "null" } */ +} + +void +bar (void) +{ + foo ((void *)0); /* { dg-warning "null" } */ +} + +int * foo_r(int a) __attribute__((returns_nonnull)); +int * bar_r(void) __attribute__((returns_nonnull)); + +int * +foo_r(int a) +{ + switch (a) + { + case 0: + return &z; + default: + return (int *)0; /* { dg-warning "null" } */ + } +} + +int * +bar_r (void) +{ + return 0; /* { dg-warning "null" } */ +} + Index: gcc/gimple-ssa-isolate-paths.c =================================================================== --- gcc/gimple-ssa-isolate-paths.c (revision 225868) +++ gcc/gimple-ssa-isolate-paths.c (working copy) @@ -329,15 +329,33 @@ find_implicit_erroneous_behaviour (void) in other blocks would require more complex path isolation code. */ if (gimple_bb (use_stmt) != bb) continue; - if (infer_nonnull_range (use_stmt, lhs, - flag_isolate_erroneous_paths_dereference, - flag_isolate_erroneous_paths_attribute)) + bool by_dereference + = infer_nonnull_range_by_dereference (use_stmt, lhs); + if (by_dereference + || infer_nonnull_range_by_attribute (use_stmt, lhs)) { + location_t loc = gimple_location (use_stmt) + ? gimple_location (use_stmt) + : gimple_phi_arg_location (phi, i); + + if (by_dereference) + { + warning_at (loc, OPT_Wnull_dereference, + "potential null pointer dereference"); + if (!flag_isolate_erroneous_paths_dereference) + continue; + } + else + { + if (!flag_isolate_erroneous_paths_attribute) + continue; + } + duplicate = isolate_path (bb, duplicate, e, use_stmt, lhs, false); /* When we remove an incoming edge, we need to reprocess the Ith element. */ @@ -379,17 +397,33 @@ find_explicit_erroneous_behaviour (void) because of jump threading and constant propagation. */ for (si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si)) { gimple stmt = gsi_stmt (si); - /* By passing null_pointer_node, we can use infer_nonnull_range - to detect explicit NULL pointer dereferences and other uses - where a non-NULL value is required. */ - if (infer_nonnull_range (stmt, null_pointer_node, - flag_isolate_erroneous_paths_dereference, - flag_isolate_erroneous_paths_attribute)) + /* By passing null_pointer_node, we can use the + infer_nonnull_range functions to detect explicit NULL + pointer dereferences and other uses where a non-NULL + value is required. */ + + bool by_dereference + = infer_nonnull_range_by_dereference (stmt, null_pointer_node); + if (by_dereference + || infer_nonnull_range_by_attribute (stmt, null_pointer_node)) { + if (by_dereference) + { + warning_at (gimple_location (stmt), OPT_Wnull_dereference, + "null pointer dereference"); + if (!flag_isolate_erroneous_paths_dereference) + continue; + } + else + { + if (!flag_isolate_erroneous_paths_attribute) + continue; + } + insert_trap_and_remove_trailing_statements (&si, null_pointer_node); /* And finally, remove all outgoing edges from BB. */ edge e; @@ -532,11 +566,12 @@ public: virtual bool gate (function *) { /* If we do not have a suitable builtin function for the trap statement, then do not perform the optimization. */ return (flag_isolate_erroneous_paths_dereference != 0 - || flag_isolate_erroneous_paths_attribute != 0); + || flag_isolate_erroneous_paths_attribute != 0 + || warn_null_dereference); } virtual unsigned int execute (function *) { return gimple_ssa_isolate_erroneous_paths (); Index: gcc/ubsan.c =================================================================== --- gcc/ubsan.c (revision 225868) +++ gcc/ubsan.c (working copy) @@ -1613,11 +1613,11 @@ instrument_nonnull_arg (gimple_stmt_iter loc[1] = UNKNOWN_LOCATION; for (unsigned int i = 0; i < gimple_call_num_args (stmt); i++) { tree arg = gimple_call_arg (stmt, i); if (POINTER_TYPE_P (TREE_TYPE (arg)) - && infer_nonnull_range (stmt, arg, false, true)) + && infer_nonnull_range_by_attribute (stmt, arg)) { gimple g; if (!is_gimple_val (arg)) { g = gimple_build_assign (make_ssa_name (TREE_TYPE (arg)), arg); @@ -1678,11 +1678,11 @@ instrument_nonnull_return (gimple_stmt_i loc[0] = gimple_location (stmt); loc[1] = UNKNOWN_LOCATION; if (arg && POINTER_TYPE_P (TREE_TYPE (arg)) && is_gimple_val (arg) - && infer_nonnull_range (stmt, arg, false, true)) + && infer_nonnull_range_by_attribute (stmt, arg)) { basic_block then_bb, fallthru_bb; *gsi = create_cond_insert_point (gsi, true, false, true, &then_bb, &fallthru_bb); gimple g = gimple_build_cond (EQ_EXPR, arg, Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 225868) +++ gcc/common.opt (working copy) @@ -590,10 +590,15 @@ Common RejectNegative Joined Warning Und Wlarger-than= Common RejectNegative Joined UInteger Warning -Wlarger-than= Warn if an object is larger than bytes +Wnull-dereference +Common Var(warn_null_dereference) Warning EnabledBy(Wall) +Warn if the compiler detects paths which trigger erroneous or undefined +behaviour due to dereferencing a NULL pointer. + Wunsafe-loop-optimizations Common Var(warn_unsafe_loop_optimizations) Warning Warn if the loop cannot be optimized due to nontrivial assumptions. Wmissing-noreturn Index: gcc/gimple.c =================================================================== --- gcc/gimple.c (revision 225868) +++ gcc/gimple.c (working copy) @@ -2616,35 +2616,52 @@ check_loadstore (gimple, tree op, tree, && operand_equal_p (TREE_OPERAND (op, 0), (tree)data, 0)) return true; return false; } -/* If OP can be inferred to be non-NULL after STMT executes, return true. - DEREFERENCE is TRUE if we can use a pointer dereference to infer a - non-NULL range, FALSE otherwise. - - ATTRIBUTE is TRUE if we can use attributes to infer a non-NULL range - for function arguments and return values. FALSE otherwise. */ +/* Return true if OP can be inferred to be non-NULL after STMT executes, + either by using a pointer dereference or attributes. */ +bool +infer_nonnull_range (gimple stmt, tree op) +{ + return infer_nonnull_range_by_dereference (stmt, op) + || infer_nonnull_range_by_attribute (stmt, op); +} +/* Return true if OP can be inferred to be non-NULL after STMT + executes by using a pointer dereference. */ bool -infer_nonnull_range (gimple stmt, tree op, bool dereference, bool attribute) +infer_nonnull_range_by_dereference (gimple stmt, tree op) { /* 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 || !POINTER_TYPE_P (TREE_TYPE (op)) || gimple_code (stmt) == GIMPLE_ASM) return false; - if (dereference - && walk_stmt_load_store_ops (stmt, (void *)op, - check_loadstore, check_loadstore)) + if (walk_stmt_load_store_ops (stmt, (void *)op, + check_loadstore, check_loadstore)) return true; - if (attribute - && is_gimple_call (stmt) && !gimple_call_internal_p (stmt)) + return false; +} + +/* Return true if OP can be inferred to be a non-NULL after STMT + executes by using attributes. */ +bool +infer_nonnull_range_by_attribute (gimple stmt, tree op) +{ + /* 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 + || !POINTER_TYPE_P (TREE_TYPE (op)) + || gimple_code (stmt) == GIMPLE_ASM) + return false; + + if (is_gimple_call (stmt) && !gimple_call_internal_p (stmt)) { tree fntype = gimple_call_fntype (stmt); tree attrs = TYPE_ATTRIBUTES (fntype); for (; attrs; attrs = TREE_CHAIN (attrs)) { @@ -2679,17 +2696,16 @@ infer_nonnull_range (gimple stmt, tree o } } /* If this function is marked as returning non-null, then we can infer OP is non-null if it is used in the return statement. */ - if (attribute) - if (greturn *return_stmt = dyn_cast (stmt)) - if (gimple_return_retval (return_stmt) - && operand_equal_p (gimple_return_retval (return_stmt), op, 0) - && lookup_attribute ("returns_nonnull", - TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)))) - return true; + if (greturn *return_stmt = dyn_cast (stmt)) + if (gimple_return_retval (return_stmt) + && operand_equal_p (gimple_return_retval (return_stmt), op, 0) + && lookup_attribute ("returns_nonnull", + TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)))) + return true; return false; } /* Compare two case labels. Because the front end should already have Index: gcc/gimple.h =================================================================== --- gcc/gimple.h (revision 225868) +++ gcc/gimple.h (working copy) @@ -1399,11 +1399,13 @@ extern bool gimple_call_builtin_p (const extern bool gimple_call_builtin_p (const_gimple, enum built_in_class); extern bool gimple_call_builtin_p (const_gimple, enum built_in_function); extern bool gimple_asm_clobbers_memory_p (const gasm *); extern void dump_decl_set (FILE *, bitmap); extern bool nonfreeing_call_p (gimple); -extern bool infer_nonnull_range (gimple, tree, bool, bool); +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 void sort_case_labels (vec); extern void preprocess_case_label_vec_for_gimple (vec, tree, tree *); extern void gimple_seq_set_location (gimple_seq, location_t); extern void gimple_seq_discard (gimple_seq); extern void maybe_remove_unused_call_args (struct function *, gimple);