tree-optimization/104475 - bogus -Wstringop-overflow

Message ID 20221207112501.989ED136B4@imap1.suse-dmz.suse.de
State New
Headers
Series tree-optimization/104475 - bogus -Wstringop-overflow |

Commit Message

Richard Biener Dec. 7, 2022, 11:25 a.m. UTC
  The following avoids a bogus -Wstringop-overflow diagnostic by
properly recognizing that &d->m_mutex cannot be nullptr in C++
even if m_mutex is at offset zero.  The frontend already diagnoses
a &d->m_mutex != nullptr comparison and the following transfers
this knowledge to the middle-end which sees &d->m_mutex as
simple pointer arithmetic.  The new ADDR_NONZERO flag on an
ADDR_EXPR is used to carry this information and it's checked in
the tree_expr_nonzero_p API which causes this to be folded early.

To avoid the bogus diagnostic this avoids separating the nullptr
path via jump-threading by eliminating the nullptr check.

I'd appreciate C++ folks picking this up and put the flag on
the appropriate ADDR_EXPRs - I've tried avoiding to put it on
all of them and didn't try hard to mimick what -Waddress warns
on (the code is big, maybe some refactoring would help but also
not sure what exactly the C++ standard constraints are here).

Bootstrapped and tested on x86_64-unknown-linux-gnu.

Thanks,
Richard.

	PR tree-optimization/104475
gcc/
	* tree-core.h: Document use of nothrow_flag on ADDR_EXPR.
	* tree.h (ADDR_NONZERO): New.
	* fold-const.cc (tree_single_nonzero_warnv_p): Check
	ADDR_NONZERO.

gcc/cp/
	* typeck.cc (cp_build_addr_expr_1): Set ADDR_NONZERO
	on the built address if it is of a COMPONENT_REF.

	* g++.dg/opt/pr104475.C: New testcase.
---
 gcc/cp/typeck.cc                    |  3 +++
 gcc/fold-const.cc                   |  4 +++-
 gcc/testsuite/g++.dg/opt/pr104475.C | 12 ++++++++++++
 gcc/tree-core.h                     |  3 +++
 gcc/tree.h                          |  4 ++++
 5 files changed, 25 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/opt/pr104475.C
  

Comments

Jason Merrill Jan. 17, 2023, 5:37 p.m. UTC | #1
On 12/7/22 06:25, Richard Biener wrote:
> The following avoids a bogus -Wstringop-overflow diagnostic by
> properly recognizing that &d->m_mutex cannot be nullptr in C++
> even if m_mutex is at offset zero.  The frontend already diagnoses
> a &d->m_mutex != nullptr comparison and the following transfers
> this knowledge to the middle-end which sees &d->m_mutex as
> simple pointer arithmetic.  The new ADDR_NONZERO flag on an
> ADDR_EXPR is used to carry this information and it's checked in
> the tree_expr_nonzero_p API which causes this to be folded early.
> 
> To avoid the bogus diagnostic this avoids separating the nullptr
> path via jump-threading by eliminating the nullptr check.
> 
> I'd appreciate C++ folks picking this up and put the flag on
> the appropriate ADDR_EXPRs - I've tried avoiding to put it on
> all of them and didn't try hard to mimick what -Waddress warns
> on (the code is big, maybe some refactoring would help but also
> not sure what exactly the C++ standard constraints are here).

This is allowed by the standard, at least after CWG2535, but we need to 
check -fsanitize=null before asserting that the address is non-null. 
With that elaboration, a flag on the ADDR_EXPR may not be a convenient 
way to express the property?

> Bootstrapped and tested on x86_64-unknown-linux-gnu.
> 
> Thanks,
> Richard.
> 
> 	PR tree-optimization/104475
> gcc/
> 	* tree-core.h: Document use of nothrow_flag on ADDR_EXPR.
> 	* tree.h (ADDR_NONZERO): New.
> 	* fold-const.cc (tree_single_nonzero_warnv_p): Check
> 	ADDR_NONZERO.
> 
> gcc/cp/
> 	* typeck.cc (cp_build_addr_expr_1): Set ADDR_NONZERO
> 	on the built address if it is of a COMPONENT_REF.
> 
> 	* g++.dg/opt/pr104475.C: New testcase.
> ---
>   gcc/cp/typeck.cc                    |  3 +++
>   gcc/fold-const.cc                   |  4 +++-
>   gcc/testsuite/g++.dg/opt/pr104475.C | 12 ++++++++++++
>   gcc/tree-core.h                     |  3 +++
>   gcc/tree.h                          |  4 ++++
>   5 files changed, 25 insertions(+), 1 deletion(-)
>   create mode 100644 gcc/testsuite/g++.dg/opt/pr104475.C
> 
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 7dfe5acc67e..3563750803e 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -7232,6 +7232,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
>         gcc_assert (same_type_ignoring_top_level_qualifiers_p
>   		  (TREE_TYPE (object), decl_type_context (field)));
>         val = build_address (arg);
> +      if (TREE_CODE (val) == ADDR_EXPR
> +	  && TREE_CODE (TREE_OPERAND (val, 0)) == COMPONENT_REF)
> +	ADDR_NONZERO (val) = 1;
>       }
>   
>     if (TYPE_PTR_P (argtype)
> diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
> index e80be8049e1..cdfe3f50ae3 100644
> --- a/gcc/fold-const.cc
> +++ b/gcc/fold-const.cc
> @@ -15308,8 +15308,10 @@ tree_single_nonzero_warnv_p (tree t, bool *strict_overflow_p)
>   
>       case ADDR_EXPR:
>         {
> -	tree base = TREE_OPERAND (t, 0);
> +	if (ADDR_NONZERO (t))
> +	  return true;
>   
> +	tree base = TREE_OPERAND (t, 0);
>   	if (!DECL_P (base))
>   	  base = get_base_address (base);
>   
> diff --git a/gcc/testsuite/g++.dg/opt/pr104475.C b/gcc/testsuite/g++.dg/opt/pr104475.C
> new file mode 100644
> index 00000000000..013c70302c6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/opt/pr104475.C
> @@ -0,0 +1,12 @@
> +// { dg-do compile }
> +// { dg-require-effective-target c++11 }
> +// { dg-options "-O -Waddress -fdump-tree-original" }
> +
> +struct X { int i; };
> +
> +bool foo (struct X *p)
> +{
> +  return &p->i != nullptr; /* { dg-warning "never be NULL" } */
> +}
> +
> +/* { dg-final { scan-tree-dump "return <retval> = 1;" "original" } } */
> diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> index e146b133dbd..303e25b5df6 100644
> --- a/gcc/tree-core.h
> +++ b/gcc/tree-core.h
> @@ -1376,6 +1376,9 @@ struct GTY(()) tree_base {
>          TREE_THIS_NOTRAP in
>             INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF, ARRAY_RANGE_REF
>   
> +       ADDR_NONZERO in
> +	  ADDR_EXPR
> +
>          SSA_NAME_IN_FREE_LIST in
>             SSA_NAME
>   
> diff --git a/gcc/tree.h b/gcc/tree.h
> index 23223ca0c87..1c810c0b21b 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -876,6 +876,10 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
>     (TREE_CHECK5 (NODE, INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,	\
>   		ARRAY_RANGE_REF)->base.nothrow_flag)
>   
> +/* Nozero means this ADDR_EXPR is not equal to NULL.  */
> +#define ADDR_NONZERO(NODE) \
> +  (TREE_CHECK (NODE, ADDR_EXPR)->base.nothrow_flag)
> +
>   /* In a VAR_DECL, PARM_DECL or FIELD_DECL, or any kind of ..._REF node,
>      nonzero means it may not be the lhs of an assignment.
>      Nonzero in a FUNCTION_DECL means this function should be treated
  
Richard Biener Jan. 18, 2023, 8:06 a.m. UTC | #2
On Tue, 17 Jan 2023, Jason Merrill wrote:

> On 12/7/22 06:25, Richard Biener wrote:
> > The following avoids a bogus -Wstringop-overflow diagnostic by
> > properly recognizing that &d->m_mutex cannot be nullptr in C++
> > even if m_mutex is at offset zero.  The frontend already diagnoses
> > a &d->m_mutex != nullptr comparison and the following transfers
> > this knowledge to the middle-end which sees &d->m_mutex as
> > simple pointer arithmetic.  The new ADDR_NONZERO flag on an
> > ADDR_EXPR is used to carry this information and it's checked in
> > the tree_expr_nonzero_p API which causes this to be folded early.
> > 
> > To avoid the bogus diagnostic this avoids separating the nullptr
> > path via jump-threading by eliminating the nullptr check.
> > 
> > I'd appreciate C++ folks picking this up and put the flag on
> > the appropriate ADDR_EXPRs - I've tried avoiding to put it on
> > all of them and didn't try hard to mimick what -Waddress warns
> > on (the code is big, maybe some refactoring would help but also
> > not sure what exactly the C++ standard constraints are here).
> 
> This is allowed by the standard, at least after CWG2535, but we need to check
> -fsanitize=null before asserting that the address is non-null. With that
> elaboration, a flag on the ADDR_EXPR may not be a convenient way to express
> the property?

Adding a flag on the ADDR_EXPR was mostly out of caution for other
languages that do not have this guarantee (it seems C has a similar
guarantee at least) and for the middle-end (accidentially) producing
such expressions.  That is, I intended to set the flag on ADDR_EXPRs
written by the user as opposed to those created artificially.

I noticed the &* contraction rule and wondered how to conservatively
enforce that - I suppose we'd rely on the frontend to never actually
produce the ADDR_EXPR here.

That said, we could re-define GENERIC/GIMPLE here to the extent
that ADDR_EXPR of a COMPONENT_REF (or all handled components?)
is never nullptr when the target specifies nullptr is not a valid
object address.  We currently already assert there's a valid
object for &p->x if x lives at non-zero offset, so the case we
fail to handle is specifically _only_ the one the component is
at offset zero.  Note &p->x != (void *)4 isn't currently optimized
when x is at offset 4 even though *p would be at address zero
and -Waddress also doesn't diagnose this case - we could
canonicalize this to to p != (void *)0 but then we cannot
treat this as false anymore because of the address-taking of a component.

Richard.

> > Bootstrapped and tested on x86_64-unknown-linux-gnu.
> > 
> > Thanks,
> > Richard.
> > 
> > 	PR tree-optimization/104475
> > gcc/
> >  * tree-core.h: Document use of nothrow_flag on ADDR_EXPR.
> >  * tree.h (ADDR_NONZERO): New.
> >  * fold-const.cc (tree_single_nonzero_warnv_p): Check
> >  ADDR_NONZERO.
> > 
> > gcc/cp/
> >  * typeck.cc (cp_build_addr_expr_1): Set ADDR_NONZERO
> >  on the built address if it is of a COMPONENT_REF.
> > 
> > 	* g++.dg/opt/pr104475.C: New testcase.
> > ---
> >   gcc/cp/typeck.cc                    |  3 +++
> >   gcc/fold-const.cc                   |  4 +++-
> >   gcc/testsuite/g++.dg/opt/pr104475.C | 12 ++++++++++++
> >   gcc/tree-core.h                     |  3 +++
> >   gcc/tree.h                          |  4 ++++
> >   5 files changed, 25 insertions(+), 1 deletion(-)
> >   create mode 100644 gcc/testsuite/g++.dg/opt/pr104475.C
> > 
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index 7dfe5acc67e..3563750803e 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -7232,6 +7232,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue,
> > tsubst_flags_t complain)
> >         gcc_assert (same_type_ignoring_top_level_qualifiers_p
> >         		  (TREE_TYPE (object), decl_type_context (field)));
> >         val = build_address (arg);
> > +      if (TREE_CODE (val) == ADDR_EXPR
> > +	  && TREE_CODE (TREE_OPERAND (val, 0)) == COMPONENT_REF)
> > +	ADDR_NONZERO (val) = 1;
> >       }
> >   
> >     if (TYPE_PTR_P (argtype)
> > diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
> > index e80be8049e1..cdfe3f50ae3 100644
> > --- a/gcc/fold-const.cc
> > +++ b/gcc/fold-const.cc
> > @@ -15308,8 +15308,10 @@ tree_single_nonzero_warnv_p (tree t, bool
> > *strict_overflow_p)
> >   
> >       case ADDR_EXPR:
> >         {
> > -	tree base = TREE_OPERAND (t, 0);
> > +	if (ADDR_NONZERO (t))
> > +	  return true;
> >   +	tree base = TREE_OPERAND (t, 0);
> >    if (!DECL_P (base))
> >      base = get_base_address (base);
> >   diff --git a/gcc/testsuite/g++.dg/opt/pr104475.C
> > b/gcc/testsuite/g++.dg/opt/pr104475.C
> > new file mode 100644
> > index 00000000000..013c70302c6
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/opt/pr104475.C
> > @@ -0,0 +1,12 @@
> > +// { dg-do compile }
> > +// { dg-require-effective-target c++11 }
> > +// { dg-options "-O -Waddress -fdump-tree-original" }
> > +
> > +struct X { int i; };
> > +
> > +bool foo (struct X *p)
> > +{
> > +  return &p->i != nullptr; /* { dg-warning "never be NULL" } */
> > +}
> > +
> > +/* { dg-final { scan-tree-dump "return <retval> = 1;" "original" } } */
> > diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> > index e146b133dbd..303e25b5df6 100644
> > --- a/gcc/tree-core.h
> > +++ b/gcc/tree-core.h
> > @@ -1376,6 +1376,9 @@ struct GTY(()) tree_base {
> >          TREE_THIS_NOTRAP in
> >             INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,
> >             ARRAY_RANGE_REF
> >   +       ADDR_NONZERO in
> > +	  ADDR_EXPR
> > +
> >          SSA_NAME_IN_FREE_LIST in
> >             SSA_NAME
> >   diff --git a/gcc/tree.h b/gcc/tree.h
> > index 23223ca0c87..1c810c0b21b 100644
> > --- a/gcc/tree.h
> > +++ b/gcc/tree.h
> > @@ -876,6 +876,10 @@ extern void omp_clause_range_check_failed (const_tree,
> > const char *, int,
> >     (TREE_CHECK5 (NODE, INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,
> >     \
> >     ARRAY_RANGE_REF)->base.nothrow_flag)
> >   +/* Nozero means this ADDR_EXPR is not equal to NULL.  */
> > +#define ADDR_NONZERO(NODE) \
> > +  (TREE_CHECK (NODE, ADDR_EXPR)->base.nothrow_flag)
> > +
> >   /* In a VAR_DECL, PARM_DECL or FIELD_DECL, or any kind of ..._REF node,
> >      nonzero means it may not be the lhs of an assignment.
> >      Nonzero in a FUNCTION_DECL means this function should be treated
> 
> 
>
  
Jason Merrill Jan. 19, 2023, 8:53 p.m. UTC | #3
On 1/18/23 03:06, Richard Biener wrote:
> On Tue, 17 Jan 2023, Jason Merrill wrote:
> 
>> On 12/7/22 06:25, Richard Biener wrote:
>>> The following avoids a bogus -Wstringop-overflow diagnostic by
>>> properly recognizing that &d->m_mutex cannot be nullptr in C++
>>> even if m_mutex is at offset zero.  The frontend already diagnoses
>>> a &d->m_mutex != nullptr comparison and the following transfers
>>> this knowledge to the middle-end which sees &d->m_mutex as
>>> simple pointer arithmetic.  The new ADDR_NONZERO flag on an
>>> ADDR_EXPR is used to carry this information and it's checked in
>>> the tree_expr_nonzero_p API which causes this to be folded early.
>>>
>>> To avoid the bogus diagnostic this avoids separating the nullptr
>>> path via jump-threading by eliminating the nullptr check.
>>>
>>> I'd appreciate C++ folks picking this up and put the flag on
>>> the appropriate ADDR_EXPRs - I've tried avoiding to put it on
>>> all of them and didn't try hard to mimick what -Waddress warns
>>> on (the code is big, maybe some refactoring would help but also
>>> not sure what exactly the C++ standard constraints are here).
>>
>> This is allowed by the standard, at least after CWG2535, but we need to check
>> -fsanitize=null before asserting that the address is non-null. With that
>> elaboration, a flag on the ADDR_EXPR may not be a convenient way to express
>> the property?
> 
> Adding a flag on the ADDR_EXPR was mostly out of caution for other
> languages that do not have this guarantee (it seems C has a similar
> guarantee at least) and for the middle-end (accidentally) producing
> such expressions.  That is, I intended to set the flag on ADDR_EXPRs
> written by the user as opposed to those created artificially.
> 
> I noticed the &* contraction rule and wondered how to conservatively
> enforce that - I suppose we'd rely on the frontend to never actually
> produce the ADDR_EXPR here.

Makes sense.

> That said, we could re-define GENERIC/GIMPLE here to the extent
> that ADDR_EXPR of a COMPONENT_REF (or all handled components?)

Not ARRAY_REF, I think; in C++ &p[0] (i.e. p+0) seems well-formed for 
null p, though any other index is undefined.

> is never nullptr when the target specifies nullptr is not a valid
> object address.  We currently already assert there's a valid
> object for &p->x if x lives at non-zero offset, so the case we
> fail to handle is specifically _only_ the one the component is
> at offset zero.  Note &p->x != (void *)4 isn't currently optimized
> when x is at offset 4 even though *p would be at address zero
> and -Waddress also doesn't diagnose this case - we could
> canonicalize this to to p != (void *)0 but then we cannot
> treat this as false anymore because of the address-taking of a component.

Any thoughts about where the -fsanitize=null check goes?

> Richard.
> 
>>> Bootstrapped and tested on x86_64-unknown-linux-gnu.
>>>
>>> Thanks,
>>> Richard.
>>>
>>> 	PR tree-optimization/104475
>>> gcc/
>>>   * tree-core.h: Document use of nothrow_flag on ADDR_EXPR.
>>>   * tree.h (ADDR_NONZERO): New.
>>>   * fold-const.cc (tree_single_nonzero_warnv_p): Check
>>>   ADDR_NONZERO.
>>>
>>> gcc/cp/
>>>   * typeck.cc (cp_build_addr_expr_1): Set ADDR_NONZERO
>>>   on the built address if it is of a COMPONENT_REF.
>>>
>>> 	* g++.dg/opt/pr104475.C: New testcase.
>>> ---
>>>    gcc/cp/typeck.cc                    |  3 +++
>>>    gcc/fold-const.cc                   |  4 +++-
>>>    gcc/testsuite/g++.dg/opt/pr104475.C | 12 ++++++++++++
>>>    gcc/tree-core.h                     |  3 +++
>>>    gcc/tree.h                          |  4 ++++
>>>    5 files changed, 25 insertions(+), 1 deletion(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/opt/pr104475.C
>>>
>>> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
>>> index 7dfe5acc67e..3563750803e 100644
>>> --- a/gcc/cp/typeck.cc
>>> +++ b/gcc/cp/typeck.cc
>>> @@ -7232,6 +7232,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue,
>>> tsubst_flags_t complain)
>>>          gcc_assert (same_type_ignoring_top_level_qualifiers_p
>>>          		  (TREE_TYPE (object), decl_type_context (field)));
>>>          val = build_address (arg);
>>> +      if (TREE_CODE (val) == ADDR_EXPR
>>> +	  && TREE_CODE (TREE_OPERAND (val, 0)) == COMPONENT_REF)
>>> +	ADDR_NONZERO (val) = 1;
>>>        }
>>>    
>>>      if (TYPE_PTR_P (argtype)
>>> diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
>>> index e80be8049e1..cdfe3f50ae3 100644
>>> --- a/gcc/fold-const.cc
>>> +++ b/gcc/fold-const.cc
>>> @@ -15308,8 +15308,10 @@ tree_single_nonzero_warnv_p (tree t, bool
>>> *strict_overflow_p)
>>>    
>>>        case ADDR_EXPR:
>>>          {
>>> -	tree base = TREE_OPERAND (t, 0);
>>> +	if (ADDR_NONZERO (t))
>>> +	  return true;
>>>    +	tree base = TREE_OPERAND (t, 0);
>>>     if (!DECL_P (base))
>>>       base = get_base_address (base);
>>>    diff --git a/gcc/testsuite/g++.dg/opt/pr104475.C
>>> b/gcc/testsuite/g++.dg/opt/pr104475.C
>>> new file mode 100644
>>> index 00000000000..013c70302c6
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/opt/pr104475.C
>>> @@ -0,0 +1,12 @@
>>> +// { dg-do compile }
>>> +// { dg-require-effective-target c++11 }
>>> +// { dg-options "-O -Waddress -fdump-tree-original" }
>>> +
>>> +struct X { int i; };
>>> +
>>> +bool foo (struct X *p)
>>> +{
>>> +  return &p->i != nullptr; /* { dg-warning "never be NULL" } */
>>> +}
>>> +
>>> +/* { dg-final { scan-tree-dump "return <retval> = 1;" "original" } } */
>>> diff --git a/gcc/tree-core.h b/gcc/tree-core.h
>>> index e146b133dbd..303e25b5df6 100644
>>> --- a/gcc/tree-core.h
>>> +++ b/gcc/tree-core.h
>>> @@ -1376,6 +1376,9 @@ struct GTY(()) tree_base {
>>>           TREE_THIS_NOTRAP in
>>>              INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,
>>>              ARRAY_RANGE_REF
>>>    +       ADDR_NONZERO in
>>> +	  ADDR_EXPR
>>> +
>>>           SSA_NAME_IN_FREE_LIST in
>>>              SSA_NAME
>>>    diff --git a/gcc/tree.h b/gcc/tree.h
>>> index 23223ca0c87..1c810c0b21b 100644
>>> --- a/gcc/tree.h
>>> +++ b/gcc/tree.h
>>> @@ -876,6 +876,10 @@ extern void omp_clause_range_check_failed (const_tree,
>>> const char *, int,
>>>      (TREE_CHECK5 (NODE, INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,
>>>      \
>>>      ARRAY_RANGE_REF)->base.nothrow_flag)
>>>    +/* Nozero means this ADDR_EXPR is not equal to NULL.  */
>>> +#define ADDR_NONZERO(NODE) \
>>> +  (TREE_CHECK (NODE, ADDR_EXPR)->base.nothrow_flag)
>>> +
>>>    /* In a VAR_DECL, PARM_DECL or FIELD_DECL, or any kind of ..._REF node,
>>>       nonzero means it may not be the lhs of an assignment.
>>>       Nonzero in a FUNCTION_DECL means this function should be treated
>>
>>
>>
>
  

Patch

diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 7dfe5acc67e..3563750803e 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7232,6 +7232,9 @@  cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
       gcc_assert (same_type_ignoring_top_level_qualifiers_p
 		  (TREE_TYPE (object), decl_type_context (field)));
       val = build_address (arg);
+      if (TREE_CODE (val) == ADDR_EXPR
+	  && TREE_CODE (TREE_OPERAND (val, 0)) == COMPONENT_REF)
+	ADDR_NONZERO (val) = 1;
     }
 
   if (TYPE_PTR_P (argtype)
diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
index e80be8049e1..cdfe3f50ae3 100644
--- a/gcc/fold-const.cc
+++ b/gcc/fold-const.cc
@@ -15308,8 +15308,10 @@  tree_single_nonzero_warnv_p (tree t, bool *strict_overflow_p)
 
     case ADDR_EXPR:
       {
-	tree base = TREE_OPERAND (t, 0);
+	if (ADDR_NONZERO (t))
+	  return true;
 
+	tree base = TREE_OPERAND (t, 0);
 	if (!DECL_P (base))
 	  base = get_base_address (base);
 
diff --git a/gcc/testsuite/g++.dg/opt/pr104475.C b/gcc/testsuite/g++.dg/opt/pr104475.C
new file mode 100644
index 00000000000..013c70302c6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/opt/pr104475.C
@@ -0,0 +1,12 @@ 
+// { dg-do compile }
+// { dg-require-effective-target c++11 }
+// { dg-options "-O -Waddress -fdump-tree-original" }
+
+struct X { int i; };
+
+bool foo (struct X *p)
+{
+  return &p->i != nullptr; /* { dg-warning "never be NULL" } */
+}
+
+/* { dg-final { scan-tree-dump "return <retval> = 1;" "original" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index e146b133dbd..303e25b5df6 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1376,6 +1376,9 @@  struct GTY(()) tree_base {
        TREE_THIS_NOTRAP in
           INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF, ARRAY_RANGE_REF
 
+       ADDR_NONZERO in
+	  ADDR_EXPR
+
        SSA_NAME_IN_FREE_LIST in
           SSA_NAME
 
diff --git a/gcc/tree.h b/gcc/tree.h
index 23223ca0c87..1c810c0b21b 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -876,6 +876,10 @@  extern void omp_clause_range_check_failed (const_tree, const char *, int,
   (TREE_CHECK5 (NODE, INDIRECT_REF, MEM_REF, TARGET_MEM_REF, ARRAY_REF,	\
 		ARRAY_RANGE_REF)->base.nothrow_flag)
 
+/* Nozero means this ADDR_EXPR is not equal to NULL.  */
+#define ADDR_NONZERO(NODE) \
+  (TREE_CHECK (NODE, ADDR_EXPR)->base.nothrow_flag)
+
 /* In a VAR_DECL, PARM_DECL or FIELD_DECL, or any kind of ..._REF node,
    nonzero means it may not be the lhs of an assignment.
    Nonzero in a FUNCTION_DECL means this function should be treated