diff mbox

enhance -Wrestrict for sprintf %s arguments

Message ID f028c9d0-d24b-e9e6-bdd1-48fe65c641b3@gmail.com
State New
Headers show

Commit Message

Martin Sebor July 2, 2017, 8 p.m. UTC
The attached patch enhances the -Wrestrict warning to detect more
than just trivial instances of overlapping copying by sprintf and
related functions.

The meat of the patch is relatively simple but because it introduces
dependencies between existing classes in the sprintf pass I had to
move the class definitions around.  That makes the changes look more
extensive than they really are.

The enhancement works by first determining the base object (or
pointer) from the destination of the sprintf call, the constant
offset into the object, and its size.  For each %s argument, it
then computes same information.  If it determines that overlap
between the two is possible it stores the information for the
directive argument (including the size of the argument) for later
processing.  After the whole call/format string has been processed,
the patch then iterates over the stored directives and their
arguments and compares the size/length of the argument against
the function's overall output.  If they overlap it issues
a warning.

Tested on x86_64-linux.

-Wrestrict is not currently included in either -Wextra or -Wall
and this patch doesn't change it even though there have been
requests to add it to one of these two options.  I'd like to do
that as a separate step.

As the next step I'd also like to extend a limited form of the
-Wrestrict enhancement to other restrict-qualified built-ins (like
strcpy), and ultimately also to user-defined functions that make
use of restrict.  I think this might perhaps best be done in
a separate pass where the computed pointer information can be
cached to avoid recomputing it for each call, but I don't expect
to be able to have the new pass (or whatever form the enhancement
might end up taking) to also handle sprintf (at least not with
the same accuracy it does now) because the sprintf data for each
format directive are not available outside the sprintf pass.

Martin

Comments

Martin Sebor July 10, 2017, 9:19 p.m. UTC | #1
Ping:

   https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00036.html

On 07/02/2017 02:00 PM, Martin Sebor wrote:
> The attached patch enhances the -Wrestrict warning to detect more
> than just trivial instances of overlapping copying by sprintf and
> related functions.
>
> The meat of the patch is relatively simple but because it introduces
> dependencies between existing classes in the sprintf pass I had to
> move the class definitions around.  That makes the changes look more
> extensive than they really are.
>
> The enhancement works by first determining the base object (or
> pointer) from the destination of the sprintf call, the constant
> offset into the object, and its size.  For each %s argument, it
> then computes same information.  If it determines that overlap
> between the two is possible it stores the information for the
> directive argument (including the size of the argument) for later
> processing.  After the whole call/format string has been processed,
> the patch then iterates over the stored directives and their
> arguments and compares the size/length of the argument against
> the function's overall output.  If they overlap it issues
> a warning.
>
> Tested on x86_64-linux.
>
> -Wrestrict is not currently included in either -Wextra or -Wall
> and this patch doesn't change it even though there have been
> requests to add it to one of these two options.  I'd like to do
> that as a separate step.
>
> As the next step I'd also like to extend a limited form of the
> -Wrestrict enhancement to other restrict-qualified built-ins (like
> strcpy), and ultimately also to user-defined functions that make
> use of restrict.  I think this might perhaps best be done in
> a separate pass where the computed pointer information can be
> cached to avoid recomputing it for each call, but I don't expect
> to be able to have the new pass (or whatever form the enhancement
> might end up taking) to also handle sprintf (at least not with
> the same accuracy it does now) because the sprintf data for each
> format directive are not available outside the sprintf pass.
>
> Martin
Martin Sebor July 18, 2017, 2:37 a.m. UTC | #2
Ping #2:

https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00036.html

> On 07/02/2017 02:00 PM, Martin Sebor wrote:
>> The attached patch enhances the -Wrestrict warning to detect more
>> than just trivial instances of overlapping copying by sprintf and
>> related functions.
>>
>> The meat of the patch is relatively simple but because it introduces
>> dependencies between existing classes in the sprintf pass I had to
>> move the class definitions around.  That makes the changes look more
>> extensive than they really are.
>>
>> The enhancement works by first determining the base object (or
>> pointer) from the destination of the sprintf call, the constant
>> offset into the object, and its size.  For each %s argument, it
>> then computes same information.  If it determines that overlap
>> between the two is possible it stores the information for the
>> directive argument (including the size of the argument) for later
>> processing.  After the whole call/format string has been processed,
>> the patch then iterates over the stored directives and their
>> arguments and compares the size/length of the argument against
>> the function's overall output.  If they overlap it issues
>> a warning.
>>
>> Tested on x86_64-linux.
>>
>> -Wrestrict is not currently included in either -Wextra or -Wall
>> and this patch doesn't change it even though there have been
>> requests to add it to one of these two options.  I'd like to do
>> that as a separate step.
>>
>> As the next step I'd also like to extend a limited form of the
>> -Wrestrict enhancement to other restrict-qualified built-ins (like
>> strcpy), and ultimately also to user-defined functions that make
>> use of restrict.  I think this might perhaps best be done in
>> a separate pass where the computed pointer information can be
>> cached to avoid recomputing it for each call, but I don't expect
>> to be able to have the new pass (or whatever form the enhancement
>> might end up taking) to also handle sprintf (at least not with
>> the same accuracy it does now) because the sprintf data for each
>> format directive are not available outside the sprintf pass.
>>
>> Martin
>
Jeff Law July 19, 2017, 6:42 a.m. UTC | #3
On 07/02/2017 02:00 PM, Martin Sebor wrote:
> The attached patch enhances the -Wrestrict warning to detect more
> than just trivial instances of overlapping copying by sprintf and
> related functions.
> 
> The meat of the patch is relatively simple but because it introduces
> dependencies between existing classes in the sprintf pass I had to
> move the class definitions around.  That makes the changes look more
> extensive than they really are.
> 
> The enhancement works by first determining the base object (or
> pointer) from the destination of the sprintf call, the constant
> offset into the object, and its size.  For each %s argument, it
> then computes same information.  If it determines that overlap
> between the two is possible it stores the information for the
> directive argument (including the size of the argument) for later
> processing.  After the whole call/format string has been processed,
> the patch then iterates over the stored directives and their
> arguments and compares the size/length of the argument against
> the function's overall output.  If they overlap it issues
> a warning.
> 
> Tested on x86_64-linux.
> 
> -Wrestrict is not currently included in either -Wextra or -Wall
> and this patch doesn't change it even though there have been
> requests to add it to one of these two options.  I'd like to do
> that as a separate step.
Yea, I think separate step is wise.

> 
> As the next step I'd also like to extend a limited form of the
> -Wrestrict enhancement to other restrict-qualified built-ins (like
> strcpy), and ultimately also to user-defined functions that make
> use of restrict.  I think this might perhaps best be done in
> a separate pass where the computed pointer information can be
> cached to avoid recomputing it for each call, but I don't expect
> to be able to have the new pass (or whatever form the enhancement
> might end up taking) to also handle sprintf (at least not with
> the same accuracy it does now) because the sprintf data for each
> format directive are not available outside the sprintf pass.
Seems reasonable.  Actual implementation will tell us for sure :-)


> 
> Martin
> 
> gcc-35503.diff
> 
> 
> PR tree-optimization/35503 - Warning about restricted pointers?
> 
> gcc/c-family/ChangeLog:
> 
> 	PR tree-optimization/35503
> 	* gcc/c-family/c-common.c (check_function_restrict): Avoid diagnosing
> 	sprintf et al. unless both -Wformat-overflow and -Wformat-truncation
> 	are disabled.
> 
> gcc/ChangeLog:
> 
> 	PR tree-optimization/35503
> 	* gimple-ssa-sprintf.c (format_result::alias_info): New struct.
> 	(directive::argno): New member.
> 	(format_result::aliases, format_result::alias_count): New data members.
> 	(format_result::append_alias): New member function.
> 	(fmtresult::dst_offset): New data member.
> 	(pass_sprintf_length::call_info::dst_origin): New data member.
> 	(pass_sprintf_length::call_info::dst_field, dst_offset): Same.
> 	(char_type_p, array_elt_at_offset, field_at_offset): New functions.
> 	(get_origin_and_offset): Same.
> 	(format_string): Call it.
> 	(format_directive): Call append_alias and set directive argument
> 	number.
> 	(pass_sprintf_length::compute_format_length): Diagnose arguments
> 	that overlap the destination buffer.
> 	(pass_sprintf_length::handle_gimple_call): Initialize new members.
> gcc/testsuite/ChangeLog:
> 
> 	PR tree-optimization/35503
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-19.c: New test.
I'm OK with the general concept of enhancing the warning.  The big
question I have is whether or not we'd be better off using the alias
oracle here rather than what appears to be rolling our own data
structures and analysis routines to describe memory objects and their
potential alias relationship.

See tree-ssa-alias.h.  In particular you're looking for ao_ref.  You may
also be intersted in the points-to solutions.  Would using that
infrastructure make sense?

jeff
Martin Sebor July 19, 2017, 4:10 p.m. UTC | #4
On 07/19/2017 12:42 AM, Jeff Law wrote:
> On 07/02/2017 02:00 PM, Martin Sebor wrote:
>> The attached patch enhances the -Wrestrict warning to detect more
>> than just trivial instances of overlapping copying by sprintf and
>> related functions.
>>
>> The meat of the patch is relatively simple but because it introduces
>> dependencies between existing classes in the sprintf pass I had to
>> move the class definitions around.  That makes the changes look more
>> extensive than they really are.
>>
>> The enhancement works by first determining the base object (or
>> pointer) from the destination of the sprintf call, the constant
>> offset into the object, and its size.  For each %s argument, it
>> then computes same information.  If it determines that overlap
>> between the two is possible it stores the information for the
>> directive argument (including the size of the argument) for later
>> processing.  After the whole call/format string has been processed,
>> the patch then iterates over the stored directives and their
>> arguments and compares the size/length of the argument against
>> the function's overall output.  If they overlap it issues
>> a warning.
>>
>> Tested on x86_64-linux.
>>
>> -Wrestrict is not currently included in either -Wextra or -Wall
>> and this patch doesn't change it even though there have been
>> requests to add it to one of these two options.  I'd like to do
>> that as a separate step.
> Yea, I think separate step is wise.
>
>>
>> As the next step I'd also like to extend a limited form of the
>> -Wrestrict enhancement to other restrict-qualified built-ins (like
>> strcpy), and ultimately also to user-defined functions that make
>> use of restrict.  I think this might perhaps best be done in
>> a separate pass where the computed pointer information can be
>> cached to avoid recomputing it for each call, but I don't expect
>> to be able to have the new pass (or whatever form the enhancement
>> might end up taking) to also handle sprintf (at least not with
>> the same accuracy it does now) because the sprintf data for each
>> format directive are not available outside the sprintf pass.
> Seems reasonable.  Actual implementation will tell us for sure :-)
>
>
>>
>> Martin
>>
>> gcc-35503.diff
>>
>>
>> PR tree-optimization/35503 - Warning about restricted pointers?
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR tree-optimization/35503
>> 	* gcc/c-family/c-common.c (check_function_restrict): Avoid diagnosing
>> 	sprintf et al. unless both -Wformat-overflow and -Wformat-truncation
>> 	are disabled.
>>
>> gcc/ChangeLog:
>>
>> 	PR tree-optimization/35503
>> 	* gimple-ssa-sprintf.c (format_result::alias_info): New struct.
>> 	(directive::argno): New member.
>> 	(format_result::aliases, format_result::alias_count): New data members.
>> 	(format_result::append_alias): New member function.
>> 	(fmtresult::dst_offset): New data member.
>> 	(pass_sprintf_length::call_info::dst_origin): New data member.
>> 	(pass_sprintf_length::call_info::dst_field, dst_offset): Same.
>> 	(char_type_p, array_elt_at_offset, field_at_offset): New functions.
>> 	(get_origin_and_offset): Same.
>> 	(format_string): Call it.
>> 	(format_directive): Call append_alias and set directive argument
>> 	number.
>> 	(pass_sprintf_length::compute_format_length): Diagnose arguments
>> 	that overlap the destination buffer.
>> 	(pass_sprintf_length::handle_gimple_call): Initialize new members.
>> gcc/testsuite/ChangeLog:
>>
>> 	PR tree-optimization/35503
>> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-19.c: New test.
> I'm OK with the general concept of enhancing the warning.  The big
> question I have is whether or not we'd be better off using the alias
> oracle here rather than what appears to be rolling our own data
> structures and analysis routines to describe memory objects and their
> potential alias relationship.
>
> See tree-ssa-alias.h.  In particular you're looking for ao_ref.  You may
> also be intersted in the points-to solutions.  Would using that
> infrastructure make sense?

This patch make only limited use of the alias analysis which
is in the current form overly broad (i.e., it answers the "may
alias" question, not the "must alias" one).  That makes it
suitable for throttling optimization but not so much to trigger
warnings.  The other challenge is that the information the
sprintf pass needs to detect overlaps is being computed in
stages as each directive is being processed, and then again
when the whole format string has been processed and the size
of the full output is known.

That said, it certainly makes sense to use the alias analysis
to its full potential when possible.  In the next step (posted
for review a few days ago:
   https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00917.html)
I take full advantage of it in addition to enhancing it to
also answer the "must alias" question.  I've been thinking
I should revisit the sprintf solution with this enhancement
(and some others I'm still working on(*)) is in place.

To give the feature broader exposure I propose to commit the
initial patch as is, and then see about improving it in the
subsequent step.

[*] since I submitted the broader -Wrestrict patch I have found
ways to further improve it too so I'll be submitting an updated
version of it.  I expect those improvements might also benefit
the printf work.  So in my mind this is evolving into a series
of patches, each making a few incremental improvements over the
last.  Does this approach make sense to you?

Martin

PS this work has presented a little bit of a chicken and egg
problem for me.  I wrote this code with very little experience
with the alias oracle in GCC, partly with the goal of learning
more about it.  I feel it definitely served that purpose and
let me more easily implement the rest of the -Wrestrict
enhancements and take better advantage of the existing
machinery (and even enhance it where it falls short).  With
that, I think improving on this approach should be possible.
In fact, I expect it to be possible to use these enhancement
to improve other diagnostics besides -Wrestrict.
Martin Sebor July 25, 2017, 3:11 a.m. UTC | #5
Ping: Jeff, please see my reply below.

On 07/19/2017 10:10 AM, Martin Sebor wrote:
> On 07/19/2017 12:42 AM, Jeff Law wrote:
>> On 07/02/2017 02:00 PM, Martin Sebor wrote:
>>> The attached patch enhances the -Wrestrict warning to detect more
>>> than just trivial instances of overlapping copying by sprintf and
>>> related functions.
>>>
>>> The meat of the patch is relatively simple but because it introduces
>>> dependencies between existing classes in the sprintf pass I had to
>>> move the class definitions around.  That makes the changes look more
>>> extensive than they really are.
>>>
>>> The enhancement works by first determining the base object (or
>>> pointer) from the destination of the sprintf call, the constant
>>> offset into the object, and its size.  For each %s argument, it
>>> then computes same information.  If it determines that overlap
>>> between the two is possible it stores the information for the
>>> directive argument (including the size of the argument) for later
>>> processing.  After the whole call/format string has been processed,
>>> the patch then iterates over the stored directives and their
>>> arguments and compares the size/length of the argument against
>>> the function's overall output.  If they overlap it issues
>>> a warning.
>>>
>>> Tested on x86_64-linux.
>>>
>>> -Wrestrict is not currently included in either -Wextra or -Wall
>>> and this patch doesn't change it even though there have been
>>> requests to add it to one of these two options.  I'd like to do
>>> that as a separate step.
>> Yea, I think separate step is wise.
>>
>>>
>>> As the next step I'd also like to extend a limited form of the
>>> -Wrestrict enhancement to other restrict-qualified built-ins (like
>>> strcpy), and ultimately also to user-defined functions that make
>>> use of restrict.  I think this might perhaps best be done in
>>> a separate pass where the computed pointer information can be
>>> cached to avoid recomputing it for each call, but I don't expect
>>> to be able to have the new pass (or whatever form the enhancement
>>> might end up taking) to also handle sprintf (at least not with
>>> the same accuracy it does now) because the sprintf data for each
>>> format directive are not available outside the sprintf pass.
>> Seems reasonable.  Actual implementation will tell us for sure :-)
>>
>>
>>>
>>> Martin
>>>
>>> gcc-35503.diff
>>>
>>>
>>> PR tree-optimization/35503 - Warning about restricted pointers?
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gcc/c-family/c-common.c (check_function_restrict): Avoid
>>> diagnosing
>>>     sprintf et al. unless both -Wformat-overflow and -Wformat-truncation
>>>     are disabled.
>>>
>>> gcc/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gimple-ssa-sprintf.c (format_result::alias_info): New struct.
>>>     (directive::argno): New member.
>>>     (format_result::aliases, format_result::alias_count): New data
>>> members.
>>>     (format_result::append_alias): New member function.
>>>     (fmtresult::dst_offset): New data member.
>>>     (pass_sprintf_length::call_info::dst_origin): New data member.
>>>     (pass_sprintf_length::call_info::dst_field, dst_offset): Same.
>>>     (char_type_p, array_elt_at_offset, field_at_offset): New functions.
>>>     (get_origin_and_offset): Same.
>>>     (format_string): Call it.
>>>     (format_directive): Call append_alias and set directive argument
>>>     number.
>>>     (pass_sprintf_length::compute_format_length): Diagnose arguments
>>>     that overlap the destination buffer.
>>>     (pass_sprintf_length::handle_gimple_call): Initialize new members.
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-19.c: New test.
>> I'm OK with the general concept of enhancing the warning.  The big
>> question I have is whether or not we'd be better off using the alias
>> oracle here rather than what appears to be rolling our own data
>> structures and analysis routines to describe memory objects and their
>> potential alias relationship.
>>
>> See tree-ssa-alias.h.  In particular you're looking for ao_ref.  You may
>> also be intersted in the points-to solutions.  Would using that
>> infrastructure make sense?
>
> This patch make only limited use of the alias analysis which
> is in the current form overly broad (i.e., it answers the "may
> alias" question, not the "must alias" one).  That makes it
> suitable for throttling optimization but not so much to trigger
> warnings.  The other challenge is that the information the
> sprintf pass needs to detect overlaps is being computed in
> stages as each directive is being processed, and then again
> when the whole format string has been processed and the size
> of the full output is known.
>
> That said, it certainly makes sense to use the alias analysis
> to its full potential when possible.  In the next step (posted
> for review a few days ago:
>   https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00917.html)
> I take full advantage of it in addition to enhancing it to
> also answer the "must alias" question.  I've been thinking
> I should revisit the sprintf solution with this enhancement
> (and some others I'm still working on(*)) is in place.
>
> To give the feature broader exposure I propose to commit the
> initial patch as is, and then see about improving it in the
> subsequent step.
>
> [*] since I submitted the broader -Wrestrict patch I have found
> ways to further improve it too so I'll be submitting an updated
> version of it.  I expect those improvements might also benefit
> the printf work.  So in my mind this is evolving into a series
> of patches, each making a few incremental improvements over the
> last.  Does this approach make sense to you?
>
> Martin
>
> PS this work has presented a little bit of a chicken and egg
> problem for me.  I wrote this code with very little experience
> with the alias oracle in GCC, partly with the goal of learning
> more about it.  I feel it definitely served that purpose and
> let me more easily implement the rest of the -Wrestrict
> enhancements and take better advantage of the existing
> machinery (and even enhance it where it falls short).  With
> that, I think improving on this approach should be possible.
> In fact, I expect it to be possible to use these enhancement
> to improve other diagnostics besides -Wrestrict.
>
Jeff Law July 26, 2017, 7:30 p.m. UTC | #6
On 07/19/2017 10:10 AM, Martin Sebor wrote:
> On 07/19/2017 12:42 AM, Jeff Law wrote:
>> On 07/02/2017 02:00 PM, Martin Sebor wrote:
>>> The attached patch enhances the -Wrestrict warning to detect more
>>> than just trivial instances of overlapping copying by sprintf and
>>> related functions.
>>>
>>> The meat of the patch is relatively simple but because it introduces
>>> dependencies between existing classes in the sprintf pass I had to
>>> move the class definitions around.  That makes the changes look more
>>> extensive than they really are.
>>>
>>> The enhancement works by first determining the base object (or
>>> pointer) from the destination of the sprintf call, the constant
>>> offset into the object, and its size.  For each %s argument, it
>>> then computes same information.  If it determines that overlap
>>> between the two is possible it stores the information for the
>>> directive argument (including the size of the argument) for later
>>> processing.  After the whole call/format string has been processed,
>>> the patch then iterates over the stored directives and their
>>> arguments and compares the size/length of the argument against
>>> the function's overall output.  If they overlap it issues
>>> a warning.
>>>
>>> Tested on x86_64-linux.
>>>
>>> -Wrestrict is not currently included in either -Wextra or -Wall
>>> and this patch doesn't change it even though there have been
>>> requests to add it to one of these two options.  I'd like to do
>>> that as a separate step.
>> Yea, I think separate step is wise.
>>
>>>
>>> As the next step I'd also like to extend a limited form of the
>>> -Wrestrict enhancement to other restrict-qualified built-ins (like
>>> strcpy), and ultimately also to user-defined functions that make
>>> use of restrict.  I think this might perhaps best be done in
>>> a separate pass where the computed pointer information can be
>>> cached to avoid recomputing it for each call, but I don't expect
>>> to be able to have the new pass (or whatever form the enhancement
>>> might end up taking) to also handle sprintf (at least not with
>>> the same accuracy it does now) because the sprintf data for each
>>> format directive are not available outside the sprintf pass.
>> Seems reasonable.  Actual implementation will tell us for sure :-)
>>
>>
>>>
>>> Martin
>>>
>>> gcc-35503.diff
>>>
>>>
>>> PR tree-optimization/35503 - Warning about restricted pointers?
>>>
>>> gcc/c-family/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gcc/c-family/c-common.c (check_function_restrict): Avoid
>>> diagnosing
>>>     sprintf et al. unless both -Wformat-overflow and -Wformat-truncation
>>>     are disabled.
>>>
>>> gcc/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gimple-ssa-sprintf.c (format_result::alias_info): New struct.
>>>     (directive::argno): New member.
>>>     (format_result::aliases, format_result::alias_count): New data
>>> members.
>>>     (format_result::append_alias): New member function.
>>>     (fmtresult::dst_offset): New data member.
>>>     (pass_sprintf_length::call_info::dst_origin): New data member.
>>>     (pass_sprintf_length::call_info::dst_field, dst_offset): Same.
>>>     (char_type_p, array_elt_at_offset, field_at_offset): New functions.
>>>     (get_origin_and_offset): Same.
>>>     (format_string): Call it.
>>>     (format_directive): Call append_alias and set directive argument
>>>     number.
>>>     (pass_sprintf_length::compute_format_length): Diagnose arguments
>>>     that overlap the destination buffer.
>>>     (pass_sprintf_length::handle_gimple_call): Initialize new members.
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR tree-optimization/35503
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-19.c: New test.
>> I'm OK with the general concept of enhancing the warning.  The big
>> question I have is whether or not we'd be better off using the alias
>> oracle here rather than what appears to be rolling our own data
>> structures and analysis routines to describe memory objects and their
>> potential alias relationship.
>>
>> See tree-ssa-alias.h.  In particular you're looking for ao_ref.  You may
>> also be intersted in the points-to solutions.  Would using that
>> infrastructure make sense?
> 
> This patch make only limited use of the alias analysis which
> is in the current form overly broad (i.e., it answers the "may
> alias" question, not the "must alias" one).  That makes it
> suitable for throttling optimization but not so much to trigger
> warnings.  The other challenge is that the information the
> sprintf pass needs to detect overlaps is being computed in
> stages as each directive is being processed, and then again
> when the whole format string has been processed and the size
> of the full output is known.
I thought we had a must-alias query as well.  Though it may not be
properly named :-)    Hmm, perhaps not, DSE uses stmt_kills_ref_p which
seems to do most of the necessary checks inline.


> 
> That said, it certainly makes sense to use the alias analysis
> to its full potential when possible.  In the next step (posted
> for review a few days ago:
>   https://gcc.gnu.org/ml/gcc-patches/2017-07/msg00917.html)
On the list :-)

> 
> [*] since I submitted the broader -Wrestrict patch I have found
> ways to further improve it too so I'll be submitting an updated
> version of it.  I expect those improvements might also benefit
> the printf work.  So in my mind this is evolving into a series
> of patches, each making a few incremental improvements over the
> last.  Does this approach make sense to you?
Yea, let's get the basic stuff in and working and iterate on the
improvements.

> 
> Martin
> 
> PS this work has presented a little bit of a chicken and egg
> problem for me.  I wrote this code with very little experience
> with the alias oracle in GCC, partly with the goal of learning
> more about it.  I feel it definitely served that purpose and
> let me more easily implement the rest of the -Wrestrict
> enhancements and take better advantage of the existing
> machinery (and even enhance it where it falls short).  With
> that, I think improving on this approach should be possible.
> In fact, I expect it to be possible to use these enhancement
> to improve other diagnostics besides -Wrestrict.
Excellent!

jeff
diff mbox

Patch

PR tree-optimization/35503 - Warning about restricted pointers?

gcc/c-family/ChangeLog:

	PR tree-optimization/35503
	* gcc/c-family/c-common.c (check_function_restrict): Avoid diagnosing
	sprintf et al. unless both -Wformat-overflow and -Wformat-truncation
	are disabled.

gcc/ChangeLog:

	PR tree-optimization/35503
	* gimple-ssa-sprintf.c (format_result::alias_info): New struct.
	(directive::argno): New member.
	(format_result::aliases, format_result::alias_count): New data members.
	(format_result::append_alias): New member function.
	(fmtresult::dst_offset): New data member.
	(pass_sprintf_length::call_info::dst_origin): New data member.
	(pass_sprintf_length::call_info::dst_field, dst_offset): Same.
	(char_type_p, array_elt_at_offset, field_at_offset): New functions.
	(get_origin_and_offset): Same.
	(format_string): Call it.
	(format_directive): Call append_alias and set directive argument
	number.
	(pass_sprintf_length::compute_format_length): Diagnose arguments
	that overlap the destination buffer.
	(pass_sprintf_length::handle_gimple_call): Initialize new members.
gcc/testsuite/ChangeLog:

	PR tree-optimization/35503
	* gcc.dg/tree-ssa/builtin-sprintf-warn-19.c: New test.

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 4395e51..406ebc4 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5266,6 +5266,24 @@  check_function_restrict (const_tree fndecl, const_tree fntype,
   else
     parms = TYPE_ARG_TYPES (fntype);
 
+  if (DECL_BUILT_IN (fndecl)
+      && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL)
+    {
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_SPRINTF:
+	case BUILT_IN_SPRINTF_CHK:
+	case BUILT_IN_SNPRINTF:
+	case BUILT_IN_SNPRINTF_CHK:
+	  /* These are handled in gimple-ssa-sprintf.c.  */
+	  if (warn_format_overflow || warn_format_trunc)
+	    return;
+
+	default:
+	  break;
+	}
+    }
+
   for (i = 0; i < nargs; i++)
     TREE_VISITED (argarray[i]) = 0;
 
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index f43778b..8113353 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -67,6 +67,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "cfgloop.h"
 #include "intl.h"
 #include "langhooks.h"
+#include "tree-ssa-alias.h"
 
 #include "builtins.h"
 #include "stor-layout.h"
@@ -78,6 +79,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "input.h"
 #include "toplev.h"
 #include "substring-locations.h"
+#include "gcc-rich-location.h"
 #include "diagnostic.h"
 
 /* The likely worst case value of MB_LEN_MAX for the target, large enough
@@ -93,6 +95,30 @@  along with GCC; see the file COPYING3.  If not see
 
 namespace {
 
+/* Return the value of INT_MIN for the target.  */
+
+static inline HOST_WIDE_INT
+target_int_min ()
+{
+  return tree_to_shwi (TYPE_MIN_VALUE (integer_type_node));
+}
+
+/* Return the value of INT_MAX for the target.  */
+
+static inline unsigned HOST_WIDE_INT
+target_int_max ()
+{
+  return tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node));
+}
+
+/* Return the value of SIZE_MAX for the target.  */
+
+static inline unsigned HOST_WIDE_INT
+target_size_max ()
+{
+  return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
+}
+
 const pass_data pass_data_sprintf_length = {
   GIMPLE_PASS,             // pass type
   "printf-return-value",   // pass name
@@ -178,15 +204,170 @@  struct result_range
   unsigned HOST_WIDE_INT unlikely;
 };
 
+/* Format length modifiers.  */
+
+enum format_lengths
+{
+  FMT_LEN_none,
+  FMT_LEN_hh,    // char argument
+  FMT_LEN_h,     // short
+  FMT_LEN_l,     // long
+  FMT_LEN_ll,    // long long
+  FMT_LEN_L,     // long double (and GNU long long)
+  FMT_LEN_z,     // size_t
+  FMT_LEN_t,     // ptrdiff_t
+  FMT_LEN_j      // intmax_t
+};
+
+struct fmtresult;
+
+static bool
+get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT);
+
+/* Description of a format directive.  A directive is either a plain
+   string or a conversion specification that starts with '%'.  */
+
+struct directive
+{
+  directive (const pass_sprintf_length::call_info *inf, unsigned no)
+  : info (inf), dirno (no), beg (), len (), flags (), width (), prec (),
+    modifier (), specifier (), arg (), fmtfunc () { }
+
+  /* Reference to the info structure describing the call that this
+     directive is a part of.  */
+  const pass_sprintf_length::call_info *info;
+
+  /* The 1-based directive number (for debugging).  */
+  unsigned dirno;
+
+  /* The 1-based argument number of the directive's argument ARG in
+     the function's argument list.  */
+  unsigned argno;
+
+  /* The first character of the directive and its length.  */
+  const char *beg;
+  size_t len;
+
+  /* A bitmap of flags, one for each character.  */
+  unsigned flags[256 / sizeof (int)];
+
+  /* The range of values of the specified width, or -1 if not specified.  */
+  HOST_WIDE_INT width[2];
+  /* The range of values of the specified precision, or -1 if not
+     specified.  */
+  HOST_WIDE_INT prec[2];
+
+  /* Length modifier.  */
+  format_lengths modifier;
+
+  /* Format specifier character.  */
+  char specifier;
+
+  /* The argument of the directive or null when the directive doesn't
+     take one or when none is available (such as for vararg functions).  */
+  tree arg;
+
+  /* Format conversion function that given a directive and an argument
+     returns the formatting result.  */
+  fmtresult (*fmtfunc) (const directive &, tree);
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return (flags[c / (CHAR_BIT * sizeof *flags)]
+	    & (1U << (c % (CHAR_BIT * sizeof *flags))));
+  }
+
+  /* Make a record of the format flag CHR having been used.  */
+  void set_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      |= (1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Reset the format flag CHR.  */
+  void clear_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Set both bounds of the width range to VAL.  */
+  void set_width (HOST_WIDE_INT val)
+  {
+    width[0] = width[1] = val;
+  }
+
+  /* Set the width range according to ARG, with both bounds being
+     no less than 0.  For a constant ARG set both bounds to its value
+     or 0, whichever is greater.  For a non-constant ARG in some range
+     set width to its range adjusting each bound to -1 if it's less.
+     For an indeterminate ARG set width to [0, INT_MAX].  */
+  void set_width (tree arg)
+  {
+    get_int_range (arg, width, width + 1, true, 0);
+  }
+
+  /* Set both bounds of the precision range to VAL.  */
+  void set_precision (HOST_WIDE_INT val)
+  {
+    prec[0] = prec[1] = val;
+  }
+
+  /* Set the precision range according to ARG, with both bounds being
+     no less than -1.  For a constant ARG set both bounds to its value
+     or -1 whichever is greater.  For a non-constant ARG in some range
+     set precision to its range adjusting each bound to -1 if it's less.
+     For an indeterminate ARG set precision to [-1, INT_MAX].  */
+  void set_precision (tree arg)
+  {
+    get_int_range (arg, prec, prec + 1, false, -1);
+  }
+
+  /* Return true if both width and precision are known to be
+     either constant or in some range, false otherwise.  */
+  bool known_width_and_precision () const
+  {
+    return ((width[1] < 0
+	     || (unsigned HOST_WIDE_INT)width[1] <= target_int_max ())
+	    && (prec[1] < 0
+		|| (unsigned HOST_WIDE_INT)prec[1] < target_int_max ()));
+  }
+};
+
 /* The result of a call to a formatted function.  */
 
 struct format_result
 {
+  format_result ()
+  : range (), aliases (), alias_count (), knownrange (), under4k (),
+    floating (), warned () { /* No-op.  */ }
+
+  ~format_result ()
+  {
+    XDELETEVEC (aliases);
+  }
+
   /* Range of characters written by the formatted function.
      Setting the minimum to HOST_WIDE_INT_MAX disables all
      length tracking for the remainder of the format string.  */
   result_range range;
 
+  struct alias_info
+  {
+    directive dir;          /* The directive that aliases the destination.  */
+    HOST_WIDE_INT offset;   /* The offset at which it aliases it.  */
+    result_range range;     /* The raw result of the directive.  */
+  };
+
+  /* An array of directives whose pointer argument aliases a part
+     of the destination object of the formatted function.  */
+  alias_info *aliases;
+  unsigned alias_count;
+
   /* True when the range above is obtained from known values of
      directive arguments, or bounds on the amount of output such
      as width and precision, and not the result of  heuristics that
@@ -228,6 +409,10 @@  struct format_result
 
   /* Increment the number of output characters by N.  */
   format_result& operator+= (unsigned HOST_WIDE_INT);
+
+  /* Add a directive to the sequence of those with potentially aliasing
+     arguments.  */
+  void append_alias (const directive &, HOST_WIDE_INT, const result_range &);
 };
 
 format_result&
@@ -250,28 +435,24 @@  format_result::operator+= (unsigned HOST_WIDE_INT n)
   return *this;
 }
 
-/* Return the value of INT_MIN for the target.  */
-
-static inline HOST_WIDE_INT
-target_int_min ()
+void
+format_result::append_alias (const directive &d, HOST_WIDE_INT off,
+			     const result_range &resrng)
 {
-  return tree_to_shwi (TYPE_MIN_VALUE (integer_type_node));
-}
+  unsigned cnt = alias_count + 1;
+  alias_info *ar = XNEWVEC (alias_info, cnt);
 
-/* Return the value of INT_MAX for the target.  */
+  for (unsigned i = 0; i != alias_count; ++i)
+    ar[i] = aliases[i];
 
-static inline unsigned HOST_WIDE_INT
-target_int_max ()
-{
-  return tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node));
-}
+  ar[alias_count].dir = d;
+  ar[alias_count].offset = off;
+  ar[alias_count].range = resrng;
 
-/* Return the value of SIZE_MAX for the target.  */
+  XDELETEVEC (aliases);
 
-static inline unsigned HOST_WIDE_INT
-target_size_max ()
-{
-  return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
+  alias_count = cnt;
+  aliases = ar;
 }
 
 /* A straightforward mapping from the execution character set to the host
@@ -586,22 +767,6 @@  static bool
 		     const char *, int, const char *, ...)
   = format_warning_at_substring;
 
-/* Format length modifiers.  */
-
-enum format_lengths
-{
-  FMT_LEN_none,
-  FMT_LEN_hh,    // char argument
-  FMT_LEN_h,     // short
-  FMT_LEN_l,     // long
-  FMT_LEN_ll,    // long long
-  FMT_LEN_L,     // long double (and GNU long long)
-  FMT_LEN_z,     // size_t
-  FMT_LEN_t,     // ptrdiff_t
-  FMT_LEN_j      // intmax_t
-};
-
-
 /* Description of the result of conversion either of a single directive
    or the whole format string.  */
 
@@ -610,7 +775,7 @@  struct fmtresult
   /* Construct a FMTRESULT object with all counters initialized
      to MIN.  KNOWNRANGE is set when MIN is valid.  */
   fmtresult (unsigned HOST_WIDE_INT min = HOST_WIDE_INT_MAX)
-  : argmin (), argmax (),
+  : dst_offset (HOST_WIDE_INT_MIN), argmin (), argmax (),
     knownrange (min < HOST_WIDE_INT_MAX),
     nullp ()
   {
@@ -624,7 +789,7 @@  struct fmtresult
      KNOWNRANGE is set when both MIN and MAX are valid.   */
   fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max,
 	     unsigned HOST_WIDE_INT likely = HOST_WIDE_INT_MAX)
-  : argmin (), argmax (),
+  : dst_offset (HOST_WIDE_INT_MIN), argmin (), argmax (),
     knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
     nullp ()
   {
@@ -644,6 +809,11 @@  struct fmtresult
      formats as on output.  */
   static unsigned type_max_digits (tree, int);
 
+  /* Set to the offset into the destination of the formatted function
+     call for arguments that pass such potentially "aliasing" arguments
+     to "%s" directives.  */
+  HOST_WIDE_INT dst_offset;
+
   /* The range a directive's argument is in.  */
   tree argmin, argmax;
 
@@ -754,111 +924,6 @@  fmtresult::type_max_digits (tree type, int base)
   return prec * 301 / 1000 + 1;
 }
 
-static bool
-get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT);
-
-/* Description of a format directive.  A directive is either a plain
-   string or a conversion specification that starts with '%'.  */
-
-struct directive
-{
-  /* The 1-based directive number (for debugging).  */
-  unsigned dirno;
-
-  /* The first character of the directive and its length.  */
-  const char *beg;
-  size_t len;
-
-  /* A bitmap of flags, one for each character.  */
-  unsigned flags[256 / sizeof (int)];
-
-  /* The range of values of the specified width, or -1 if not specified.  */
-  HOST_WIDE_INT width[2];
-  /* The range of values of the specified precision, or -1 if not
-     specified.  */
-  HOST_WIDE_INT prec[2];
-
-  /* Length modifier.  */
-  format_lengths modifier;
-
-  /* Format specifier character.  */
-  char specifier;
-
-  /* The argument of the directive or null when the directive doesn't
-     take one or when none is available (such as for vararg functions).  */
-  tree arg;
-
-  /* Format conversion function that given a directive and an argument
-     returns the formatting result.  */
-  fmtresult (*fmtfunc) (const directive &, tree);
-
-  /* Return True when a the format flag CHR has been used.  */
-  bool get_flag (char chr) const
-  {
-    unsigned char c = chr & 0xff;
-    return (flags[c / (CHAR_BIT * sizeof *flags)]
-	    & (1U << (c % (CHAR_BIT * sizeof *flags))));
-  }
-
-  /* Make a record of the format flag CHR having been used.  */
-  void set_flag (char chr)
-  {
-    unsigned char c = chr & 0xff;
-    flags[c / (CHAR_BIT * sizeof *flags)]
-      |= (1U << (c % (CHAR_BIT * sizeof *flags)));
-  }
-
-  /* Reset the format flag CHR.  */
-  void clear_flag (char chr)
-  {
-    unsigned char c = chr & 0xff;
-    flags[c / (CHAR_BIT * sizeof *flags)]
-      &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
-  }
-
-  /* Set both bounds of the width range to VAL.  */
-  void set_width (HOST_WIDE_INT val)
-  {
-    width[0] = width[1] = val;
-  }
-
-  /* Set the width range according to ARG, with both bounds being
-     no less than 0.  For a constant ARG set both bounds to its value
-     or 0, whichever is greater.  For a non-constant ARG in some range
-     set width to its range adjusting each bound to -1 if it's less.
-     For an indeterminate ARG set width to [0, INT_MAX].  */
-  void set_width (tree arg)
-  {
-    get_int_range (arg, width, width + 1, true, 0);
-  }
-
-  /* Set both bounds of the precision range to VAL.  */
-  void set_precision (HOST_WIDE_INT val)
-  {
-    prec[0] = prec[1] = val;
-  }
-
-  /* Set the precision range according to ARG, with both bounds being
-     no less than -1.  For a constant ARG set both bounds to its value
-     or -1 whichever is greater.  For a non-constant ARG in some range
-     set precision to its range adjusting each bound to -1 if it's less.
-     For an indeterminate ARG set precision to [-1, INT_MAX].  */
-  void set_precision (tree arg)
-  {
-    get_int_range (arg, prec, prec + 1, false, -1);
-  }
-
-  /* Return true if both width and precision are known to be
-     either constant or in some range, false otherwise.  */
-  bool known_width_and_precision () const
-  {
-    return ((width[1] < 0
-	     || (unsigned HOST_WIDE_INT)width[1] <= target_int_max ())
-	    && (prec[1] < 0
-		|| (unsigned HOST_WIDE_INT)prec[1] < target_int_max ()));
-  }
-};
-
 /* Return the logarithm of X in BASE.  */
 
 static int
@@ -986,6 +1051,18 @@  struct pass_sprintf_length::call_info
   /* Called built-in function code.  */
   built_in_function fncode;
 
+  /* The "origin" of the destination pointer argument, which is either
+     the DECL of the destination buffer being written into or a pointer
+     that points to it, plus some offset.  */
+  tree dst_origin;
+
+  /* For a destination pointing to a struct array member, the offset of
+     the member.  */
+  HOST_WIDE_INT dst_field;
+  /* The offset into the destination buffer.  */
+
+  HOST_WIDE_INT dst_offset;
+
   /* Format argument and format string extracted from it.  */
   tree format;
   const char *fmtstr;
@@ -2170,6 +2247,244 @@  format_character (const directive &dir, tree arg)
   return res.adjust_for_width_or_precision (dir.width);
 }
 
+/* Return true if TYPE is one of the three narrow character types.  */
+static inline bool
+char_type_p (tree type)
+{
+  return (type == char_type_node
+	  || type == signed_char_type_node
+	  || type == unsigned_char_type_node);
+}
+
+/* Determine the offset *INDEX of the first byte of an array element of
+   TYPE (possibly recursively) into which the byte offset OFF points.
+   On success set *INDEX to the offset of the first byte and return type.
+   Otherwise, if no such element can be found, return null.  */
+
+static tree
+array_elt_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+  gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+
+  tree eltype = type;
+  while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
+    eltype = TREE_TYPE (eltype);
+
+  if (!char_type_p (TREE_TYPE (eltype)))
+    eltype = TREE_TYPE (eltype);
+
+  if (eltype == type)
+    {
+      *index = 0;
+      return type;
+    }
+
+  HOST_WIDE_INT typsz = int_size_in_bytes (type);
+  HOST_WIDE_INT eltsz = int_size_in_bytes (eltype);
+  if (off < typsz * eltsz)
+    {
+      *index = (off / eltsz) * eltsz;
+      return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+    }
+
+  return NULL_TREE;
+}
+
+/* Determine the offset *INDEX of the first byte of a struct member of TYPE
+   (possibly recursively) into which the byte offset OFF points.  On success
+   set *INDEX to the offset of the first byte and return true.  Otherwise,
+   if no such member can be found, return false.  */
+
+static bool
+field_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+  gcc_assert (RECORD_OR_UNION_TYPE_P (type));
+
+  for (tree fld = TYPE_FIELDS (type); fld; fld = TREE_CHAIN (fld))
+    {
+      tree fldtype = TREE_TYPE (fld);
+      HOST_WIDE_INT fldoff = int_byte_position (fld);
+      /* If the size is not available the field is a flexible array
+	 member.  Treat this case as success.  */
+      HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (TYPE_SIZE_UNIT (fldtype))
+			       ? tree_to_uhwi (TYPE_SIZE_UNIT (fldtype))
+			       : off);
+
+      if (fldoff + fldsize < off)
+	continue;
+
+      if (TREE_CODE (fldtype) == ARRAY_TYPE)
+	{
+	  HOST_WIDE_INT idx = 0;
+	  if (tree ft = array_elt_at_offset (fldtype, off, &idx))
+	    fldtype = ft;
+	  else
+	    break;
+
+	  *index += idx;
+	  fldoff -= idx;
+	  off -= idx;
+	}
+
+      if (RECORD_OR_UNION_TYPE_P (fldtype))
+	{
+	  *index += fldoff;
+	  return field_at_offset (fldtype, off - fldoff, index);
+	}
+
+      *index += fldoff;
+      return true;
+    }
+
+  return false;
+}
+
+/* For an expression X of pointer type, recursively try to find the same
+   origin (object or pointer) as Y it references and return such an X.
+   When X refers to a struct member, set *FLDOFF to the offset of the
+   member from the beginning of the "most derived" object.  */
+
+static tree
+get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
+{
+  switch (TREE_CODE (x))
+    {
+    case ADDR_EXPR:
+      x = TREE_OPERAND (x, 0);
+      return get_origin_and_offset (x, fldoff, off);
+
+    case ARRAY_REF:
+      {
+	tree offset = TREE_OPERAND (x, 1);
+	HOST_WIDE_INT idx	= (tree_fits_uhwi_p (offset)
+				   ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+	tree eltype = TREE_TYPE (x);
+	if (TREE_CODE (eltype) == INTEGER_TYPE)
+	  {
+	    if (off)
+	      *off = idx;
+	  }
+	else if (idx < HOST_WIDE_INT_MAX)
+	  *fldoff += idx * int_size_in_bytes (eltype);
+	else
+	  *fldoff = idx;
+
+	x = TREE_OPERAND (x, 0);
+	return get_origin_and_offset (x, fldoff, NULL);
+      }
+
+    case MEM_REF:
+      if (off)
+	{
+	  tree offset = TREE_OPERAND (x, 1);
+	  *off = (tree_fits_uhwi_p (offset)
+		  ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+	}
+
+      x = TREE_OPERAND (x, 0);
+
+      if (off)
+	{
+	  tree xtype = TREE_TYPE (TREE_TYPE (x));
+
+	  /* The byte offset of the most basic struct member the byte
+	     offset *OFF corresponds to, or for a (multidimensional)
+	     array member, the byte offset of the array element.  */
+	  HOST_WIDE_INT index = 0;
+
+	  if ((RECORD_OR_UNION_TYPE_P (xtype)
+	       && field_at_offset (xtype, *off, &index))
+	      || (TREE_CODE (xtype) == ARRAY_TYPE
+		  && TREE_CODE (TREE_TYPE (xtype)) == ARRAY_TYPE
+		  && array_elt_at_offset (xtype, *off, &index)))
+	    {
+	      *fldoff += index;
+	      *off -= index;
+	      fldoff = NULL;
+	    }
+	}
+
+      return get_origin_and_offset (x, fldoff, NULL);
+
+    case COMPONENT_REF:
+      {
+	tree fld = TREE_OPERAND (x, 1);
+	*fldoff += int_byte_position (fld);
+
+	get_origin_and_offset (fld, fldoff, off);
+	x = TREE_OPERAND (x, 0);
+	return get_origin_and_offset (x, fldoff, off);
+      }
+
+    case SSA_NAME:
+      {
+	gimple *def = SSA_NAME_DEF_STMT (x);
+	if (is_gimple_assign (def))
+	  {
+	    tree_code code = gimple_assign_rhs_code (def);
+	    if (code == ADDR_EXPR)
+	      {
+		x = gimple_assign_rhs1 (def);
+
+		return get_origin_and_offset (x, fldoff, off);
+	      }
+
+	    if (code == POINTER_PLUS_EXPR)
+	      {
+		tree offset = gimple_assign_rhs2 (def);
+		if (off)
+		  *off = (tree_fits_uhwi_p (offset)
+			  ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+		x = gimple_assign_rhs1 (def);
+		return get_origin_and_offset (x, fldoff, NULL);
+	      }
+	    else if (code == VAR_DECL)
+	      {
+		x = gimple_assign_rhs1 (def);
+		return get_origin_and_offset (x, fldoff, off);
+	      }
+	  }
+	else if (gimple_nop_p (def) && SSA_NAME_VAR (x))
+	  x = SSA_NAME_VAR (x);
+      }
+
+    default:
+      break;
+    }
+
+  return x;
+}
+
+/* If ARG refers to the same (sub)object or array element as described
+   by DST and DST_FLD, return the byte offset into the struct member or
+   array element referenced by ARG.  Otherwise return HOST_WIDE_INT_MIN
+   to indicate that ARG and DST do not refer to the same object.  */
+
+static HOST_WIDE_INT
+alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
+{
+  /* See if the argument refers to the same base object as the destination
+     of the formatted function call, and if so, try to determine if they
+     can alias.  */
+  if (!ptr_derefs_may_alias_p (arg, dst))
+    return HOST_WIDE_INT_MIN;
+
+  /* The two arguments refer to the same object.  If they both refer
+     to a struct member, see if the members are one and the same.  */
+  HOST_WIDE_INT arg_off = 0;
+
+  HOST_WIDE_INT arg_fld = 0;
+
+  tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off);
+
+  if (arg_orig == dst && arg_fld == dst_fld)
+    return arg_off;
+
+  return HOST_WIDE_INT_MIN;
+}
+
 /* Return the minimum and maximum number of characters formatted
    by the '%s' format directive and its wide character form for
    the argument ARG.  ARG can be null (for functions such as
@@ -2180,6 +2495,13 @@  format_string (const directive &dir, tree arg)
 {
   fmtresult res;
 
+  if (warn_restrict)
+    {
+      /* See if ARG aliases the destination of the call.  */
+      res.dst_offset = alias_offset (arg, dir.info->dst_origin,
+				     dir.info->dst_field);
+    }
+
   /* Compute the range the argument's length can be in.  */
   fmtresult slen = get_string_length (arg);
   if (slen.range.min == slen.range.max
@@ -2812,6 +3134,11 @@  format_directive (const pass_sprintf_length::call_info &info,
      NUL that's appended after the format string has been processed.  */
   result_range avail_range = bytes_remaining (info.objsize, *res);
 
+  /* The argument aliases a part of the destination of the formatted
+     call at offset FMTRES.DST_OFFSET.  */
+  if (fmtres.dst_offset != HOST_WIDE_INT_MIN)
+    res->append_alias (dir, fmtres.dst_offset, fmtres.range);
+
   bool warned = res->warned;
 
   if (!warned)
@@ -3400,6 +3727,10 @@  parse_directive (pass_sprintf_length::call_info &info,
       && *argno < gimple_call_num_args (info.callstmt))
     dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
 
+  /* Set the directive argument's number to correspond to its position
+     in the formatted function call's argument list.  */
+  dir.argno = *argno;
+
   if (dump_file)
     {
       fprintf (dump_file, "  Directive %u at offset %llu: \"%.*s\"",
@@ -3467,10 +3798,11 @@  pass_sprintf_length::compute_format_length (call_info &info,
   /* The variadic argument counter.  */
   unsigned argno = info.argidx;
 
+  bool success = true;
+
   for (const char *pf = info.fmtstr; ; ++dirno)
     {
-      directive dir = directive ();
-      dir.dirno = dirno;
+      directive dir (&info, dirno);
 
       size_t n = parse_directive (info, dir, res, pf, &argno);
 
@@ -3478,18 +3810,129 @@  pass_sprintf_length::compute_format_length (call_info &info,
       if (!format_directive (info, res, dir))
 	return false;
 
-      /* Return success the directive is zero bytes long and it's
+      /* Return success when the directive is zero bytes long and it's
 	 the last think in the format string (i.e., it's the terminating
 	 nul, which isn't really a directive but handling it as one makes
 	 things simpler).  */
       if (!n)
-	return *pf == '\0';
+	{
+	  success = *pf == '\0';
+	  break;
+	}
 
       pf += n;
     }
 
+  auto_vec<int, 16> aliasarg[2];
+
+  /* Traverse the set of potentially aliasing directives and collect
+     argument numbers of those that, in fact, do or may overlap the
+     destination object.  */
+  for (unsigned i = 0; i != res->alias_count; ++i)
+    {
+      const format_result::alias_info &alias = res->aliases[i];
+
+      enum { Possible = -1, None = 0, Certain = 1 } overlap = None;
+
+      if (alias.offset == HOST_WIDE_INT_MAX
+	  || info.dst_offset == HOST_WIDE_INT_MAX)
+	overlap = Possible;
+      else if (alias.offset == info.dst_offset)
+	overlap = Certain;
+      else
+	{
+	  unsigned HOST_WIDE_INT albeg = alias.offset;
+	  unsigned HOST_WIDE_INT dstbeg = info.dst_offset;
+
+	  unsigned HOST_WIDE_INT alend = albeg + alias.range.min;
+	  unsigned HOST_WIDE_INT dstend = dstbeg + res->range.min;
+
+	  if ((albeg <= dstbeg && alend > dstbeg)
+	      || (albeg >= dstbeg && albeg < dstend))
+	    overlap = Certain;
+	  else
+	    {
+	      alend = albeg + alias.range.max;
+	      if (alend < albeg)
+		alend = HOST_WIDE_INT_M1U;
+
+	      dstend = dstbeg + res->range.max;
+	      if (dstend < dstbeg)
+		dstend = HOST_WIDE_INT_M1U;
+
+	      if ((albeg <= dstbeg && alend >= dstbeg)
+		  || (albeg >= dstbeg && alend <= dstend))
+		overlap = Possible;
+	    }
+	}
+
+      if (overlap == None)
+	continue;
+
+      aliasarg[overlap != Certain].safe_push (alias.dir.argno);
+
+      /* Disable any kind of optimization.  */
+      res->range.unlikely = HOST_WIDE_INT_M1U;
+    }
+
+  tree arg0 = gimple_call_arg (info.callstmt, 0);
+  location_t loc = gimple_location (info.callstmt);
+
+  bool aliaswarn = false;
+
+  unsigned ncertain = aliasarg[0].length ();
+  unsigned npossible = aliasarg[1].length ();
+  if (ncertain && npossible)
+    {
+      /* If there are multiple arguments that overlap, some certainly
+	 and some possibly, handle both sets in a single diagnostic.  */
+      aliaswarn
+	= warning_at (loc, OPT_Wrestrict,
+		      "%qE arguments %Z and maybe %Z overlap destination "
+		      "object %qE",
+		      info.func, aliasarg[0].address (), ncertain,
+		      aliasarg[1].address (), npossible,
+		      info.dst_origin);
+    }
+  else if (ncertain)
+    {
+      /* There is only one set of two or more arguments and they all
+	 certainly overlap the destination.  */
+      aliaswarn
+	= warning_n (loc, OPT_Wrestrict, ncertain,
+		     "%qE argument %Z overlaps destination object %qE",
+		     "%qE arguments %Z overlap destination object %qE",
+		     info.func, aliasarg[0].address (), ncertain,
+		     info.dst_origin);
+    }
+  else if (npossible)
+    {
+      /* There is only one set of two or more arguments and they all
+	 may overlap (but need not).  */
+      aliaswarn
+	= warning_n (loc, OPT_Wrestrict, npossible,
+		     "%qE argument %Z may overlap destination object %qE",
+		     "%qE arguments %Z may overlap destination object %qE",
+		     info.func, aliasarg[1].address (), npossible,
+		     info.dst_origin);
+    }
+
+  if (aliaswarn && info.dst_origin != arg0)
+    {
+      /* If its location is different from the first argument of the call
+	 point either at the destination object itself or at the expression
+	 that was used to determine the overlap.  */
+      loc = (DECL_P (info.dst_origin)
+	     ? DECL_SOURCE_LOCATION (info.dst_origin)
+	     : EXPR_LOCATION (info.dst_origin));
+      if (loc != UNKNOWN_LOCATION)
+	inform (loc,
+		"destination object referenced by %<restrict%>-qualified "
+		"argument 1 was declared here");
+    }
+
   /* The complete format string was processed (with or without warnings).  */
-  return true;
+  return success;
 }
 
 /* Return the size of the object referenced by the expression DEST if
@@ -3955,12 +4398,22 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
     }
 
   info.fmtstr = get_format_string (info.format, &info.fmtloc);
+
   if (!info.fmtstr)
     return false;
 
+  if (warn_restrict)
+    {
+      /* Compute the origin of the destination pointer and its offset
+	 from the base object/pointer if possible.  */
+      info.dst_offset = 0;
+      info.dst_origin = get_origin_and_offset (dstptr, &info.dst_field,
+					       &info.dst_offset);
+    }
+
   /* The result is the number of bytes output by the formatted function,
      including the terminating NUL.  */
-  format_result res = format_result ();
+  format_result res;
 
   bool success = compute_format_length (info, &res);
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-19.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-19.c
new file mode 100644
index 0000000..88d5ee8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-19.c
@@ -0,0 +1,700 @@ 
+/* PR tree-optimization/35503 - Warning about restricted pointers?
+   { dg-do compile }
+   { dg-options "-O2 -Wformat-overflow -Wrestrict -ftrack-macro-expansion=0" }
+*/
+
+void sink (char*);
+
+#define T(d, ...) (__builtin_sprintf ((d), __VA_ARGS__), sink ((d)))
+
+void test_ptr (char *d, int i)
+{
+  T (d, "%s", d);       /* { dg-warning "overlaps" } */
+  T (d, "%s", d + 0);   /* { dg-warning "overlaps" } */
+
+  /* The following only overlaps if d[1] is non-zero.  */
+  T (d, "%s", d + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &d[0]);   /* { dg-warning "overlaps" } */
+  T (d, "%s", &d[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", d);   /* { dg-warning "overlaps" } */
+  T (d + 1, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + i, "%s", d);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", d);   /* { dg-warning "overlaps" } */
+  T (&d[1], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[i], "%s", d);   /* { dg-warning "may overlap" } */
+
+  const char *s = d;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+}
+
+void test_ptr_plus (char *d, int i)
+{
+  const char *s = d;
+
+  T (d, "%s", s++);     /* { dg-warning "overlaps" } */
+  T (d, "%s", s++);     /* { dg-warning "may overlap" } */
+  T (d, "%s", s++);     /* { dg-warning "may overlap" } */
+  T (d, "%s", s++);     /* { dg-warning "may overlap" } */
+
+  s += i;
+  T (d, "%s", s);       /* { dg-warning "may overlap" } */
+}
+
+void test_array_1_dim (int i)
+{
+  char a[] = "0123456789";
+
+  T (a, "%s", a);       /* { dg-warning "overlaps" } */
+  T (a, "%s", a + 0);   /* { dg-warning "overlaps" } */
+  T (a, "%s", a + 1);   /* { dg-warning "may overlap" } */
+  T (a, "%s", a + 2);   /* { dg-warning "may overlap" } */
+  T (a, "%s", a + i);   /* { dg-warning "may overlap" } */
+
+  T (a, "%s", &a[0]);   /* { dg-warning "overlaps" } */
+  T (a, "%s", &a[1]);   /* { dg-warning "may overlap" } */
+  T (a, "%s", &a[2]);   /* { dg-warning "may overlap" } */
+  T (a, "%s", &a[i]);   /* { dg-warning "may overlap" } */
+
+  T (a + 0, "%s", a);   /* { dg-warning "overlaps" } */
+  T (a + 1, "%s", a);   /* { dg-warning "may overlap" } */
+  T (a + 2, "%s", a);   /* { dg-warning "may overlap" } */
+  T (a + i, "%s", a);   /* { dg-warning "may overlap" } */
+
+  T (&a[0], "%s", a);   /* { dg-warning "overlaps" } */
+  T (&a[1], "%s", a);   /* { dg-warning "may overlap" } */
+  T (&a[2], "%s", a);   /* { dg-warning "may overlap" } */
+  T (&a[i], "%s", a);   /* { dg-warning "may overlap" } */
+}
+
+void test_array_2_dim (int i)
+{
+  char a[][10] = { "0123456789", "0123456789" };
+
+  T (a[0], "%s", a[0]);       /* { dg-warning "overlaps" } */
+  T (a[0], "%s", a[0] + 0);   /* { dg-warning "overlaps" } */
+  T (a[0], "%s", a[0] + 1);   /* { dg-warning "may overlap" } */
+  T (a[0], "%s", a[0] + 2);   /* { dg-warning "may overlap" } */
+  T (a[0], "%s", a[0] + i);   /* { dg-warning "may overlap" } */
+
+  T (a[0], "%s", &a[0][0]);   /* { dg-warning "overlaps" } */
+  T (a[0], "%s", &a[0][1]);   /* { dg-warning "may overlap" } */
+  T (a[0], "%s", &a[0][2]);   /* { dg-warning "may overlap" } */
+  T (a[0], "%s", &a[0][i]);   /* { dg-warning "may overlap" } */
+
+  T (a[0] + 0, "%s", a[0]);   /* { dg-warning "overlaps" } */
+  T (a[0] + 1, "%s", a[0]);   /* { dg-warning "may overlap" } */
+  T (a[0] + 2, "%s", a[0]);   /* { dg-warning "may overlap" } */
+  T (a[0] + i, "%s", a[0]);   /* { dg-warning "may overlap" } */
+
+  T (&a[0][0], "%s", a[0]);   /* { dg-warning "overlaps" } */
+  T (&a[0][1], "%s", a[0]);   /* { dg-warning "may overlap" } */
+  T (&a[0][2], "%s", a[0]);   /* { dg-warning "may overlap" } */
+  T (&a[0][i], "%s", a[0]);   /* { dg-warning "may overlap" } */
+
+
+  T (a[0], "%s", a[1]);
+  T (a[0], "%s", a[1] + 0);
+  T (a[0], "%s", a[1] + 1);
+  T (a[0], "%s", a[1] + 2);
+  T (a[0], "%s", a[1] + i);
+
+  T (a[0], "%s", &a[1][0]);
+  T (a[0], "%s", &a[1][1]);
+  T (a[0], "%s", &a[1][2]);
+
+  /* a[0] is represented as &a in Gimple, and &a[1][i] as &a + _2,
+     with _1 defined to something like 10 + _1, and _1 to i.  That
+     makes it virtually impossible to reliably determine that the
+     two pointers refer to distinct sub-arrays of the same multi-
+     dimensional array.  */
+  T (a[0], "%s", &a[1][i]);   /* { dg-bogus "overlap" "" { xfail *-*-* } } */
+
+  T (a[0] + 0, "%s", a[1]);
+  T (a[0] + 1, "%s", a[1]);
+  T (a[0] + 2, "%s", a[1]);
+  T (a[0] + i, "%s", a[1]);
+
+  T (&a[0][0], "%s", a[1]);
+  T (&a[0][1], "%s", a[1]);
+  T (&a[0][2], "%s", a[1]);
+  T (&a[0][i], "%s", a[1]);
+
+
+  T (a[1], "%s", a[0]);
+  T (a[1], "%s", a[0] + 0);
+  T (a[1], "%s", a[0] + 1);
+  T (a[1], "%s", a[0] + 2);
+  T (a[1], "%s", a[0] + i);
+
+  T (a[1], "%s", &a[0][0]);
+  T (a[1], "%s", &a[0][1]);
+  T (a[1], "%s", &a[0][2]);
+  T (a[1], "%s", &a[0][i]);
+
+  T (a[1] + 0, "%s", a[0]);
+  T (a[1] + 1, "%s", a[0]);
+  T (a[1] + 2, "%s", a[0]);
+  T (a[1] + i, "%s", a[0]);
+
+  T (&a[1][0], "%s", a[0]);
+  T (&a[1][1], "%s", a[0]);
+  T (&a[1][2], "%s", a[0]);
+  T (&a[1][i], "%s", a[0]);   /* { dg-bogus "overlap" "" { xfail *-*-* } } */
+
+
+  T (a[1], "%s", a[1]);       /* { dg-warning "overlaps" } */
+  T (a[1], "%s", a[1] + 0);   /* { dg-warning "overlaps" } */
+  T (a[1], "%s", a[1] + 1);   /* { dg-warning "may overlap" } */
+  T (a[1], "%s", a[1] + 2);   /* { dg-warning "may overlap" } */
+  T (a[1], "%s", a[1] + i);   /* { dg-warning "may overlap" } */
+
+  T (a[1], "%s", &a[1][0]);   /* { dg-warning "overlaps" } */
+  T (a[1], "%s", &a[1][1]);   /* { dg-warning "may overlap" } */
+  T (a[1], "%s", &a[1][2]);   /* { dg-warning "may overlap" } */
+  T (a[1], "%s", &a[1][i]);   /* { dg-warning "may overlap" "" { xfail *-*-* } } */
+
+  T (a[1] + 0, "%s", a[1]);   /* { dg-warning "overlaps" } */
+  T (a[1] + 1, "%s", a[1]);   /* { dg-warning "may overlap" } */
+  T (a[1] + 2, "%s", a[1]);   /* { dg-warning "may overlap" } */
+  T (a[1] + i, "%s", a[1]);   /* { dg-warning "may overlap" } */
+
+  T (&a[1][0], "%s", a[1]);   /* { dg-warning "overlaps" } */
+  T (&a[1][1], "%s", a[1]);   /* { dg-warning "may overlap" } */
+  T (&a[1][2], "%s", a[1]);   /* { dg-warning "may overlap" } */
+  T (&a[1][i], "%s", a[1]);   /* { dg-warning "may overlap" "" { xfail *-*-* } } */
+}
+
+struct S {
+  char a[4];
+  char b[4];
+};
+
+struct S2 {
+  struct S s_1;
+  struct S s_2;
+  struct S sa3[3];
+};
+
+struct S3 {
+  struct S2 s2_1;
+  struct S2 s2_2;
+
+  struct {
+    struct {
+      struct {
+	struct S sa_3[3];
+      } a_1[3];
+    } a_2[3][3];
+  } a_3[3][3][3];
+
+  char fa[];
+};
+
+void test_struct_member_array (struct S3 *s3, int i)
+{
+  char *d = s3->s2_1.s_1.a;
+
+  T (d, "%s", d);       /* { dg-warning "overlaps" } */
+  T (d, "%s", d + 0);   /* { dg-warning "overlaps" } */
+  T (d, "%s", d + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &d[0]);   /* { dg-warning "overlaps" } */
+  T (d, "%s", &d[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", d);   /* { dg-warning "overlaps" } */
+  T (d + 1, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + i, "%s", d);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", d);   /* { dg-warning "overlaps" } */
+  T (&d[1], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[i], "%s", d);   /* { dg-warning "may overlap" } */
+
+  const char *s = d;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  s = s3->s2_1.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_1.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_1.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  d = s3->s2_1.s_1.b;
+
+  s = s3->s2_1.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_1.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_1.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  d = s3->s2_2.s_1.a;
+
+  s = s3->s2_1.s_1.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_1.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_1.a;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  s = s3->s2_2.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  d = s3->s2_2.s_1.b;
+
+  s = s3->s2_1.s_1.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_1.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_1.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_1.b;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  s = s3->s2_2.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = s3->s2_2.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+}
+
+void test_struct_member_array_array (struct S3 *s3, int i)
+{
+  char *d = s3->s2_1.sa3[0].a;
+  char *s = s3->s2_1.sa3[0].a;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 0);   /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &s[0]);   /* { dg-warning "overlaps" } */
+  T (d, "%s", &s[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", s);   /* { dg-warning "overlaps" } */
+  T (d + 1, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + i, "%s", s);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", s);   /* { dg-warning "overlaps" } */
+  T (&d[1], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[i], "%s", s);   /* { dg-warning "may overlap" } */
+
+  s = s3->s2_1.sa3[0].b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 0);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  T (d, "%s", &s[0]);
+  T (d, "%s", &s[1]);
+  T (d, "%s", &s[2]);
+  T (d, "%s", &s[i]);
+
+  T (d + 0, "%s", s);
+  T (d + 1, "%s", s);
+  T (d + 2, "%s", s);
+  T (d + i, "%s", s);
+
+  T (&d[0], "%s", s);
+  T (&d[1], "%s", s);
+  T (&d[2], "%s", s);
+  T (&d[i], "%s", s);
+}
+
+void test_struct_member_nested_array (struct S3 *s3, int i)
+{
+#define PFX(sfx) s3->a_3[i3_0][i3_1][i3_2]		\
+    .a_2[i2_0][i2_1]					\
+    .a_1[i1_0].sfx
+
+#define TT(d, s)				\
+  T (PFX (d), "%s", PFX (s));			\
+  T (PFX (d), "%s", &PFX (s)[0]);		\
+  T (PFX (d), "%s", &PFX (s)[1]);		\
+  T (PFX (d), "%s", &PFX (s)[2]);		\
+  T (PFX (d), "%s", &PFX (s)[i])		\
+
+#define T1(i0)	do						\
+    {								\
+      enum {i1_0 = i0 };					\
+								\
+      TT (sa_3[0].a, sa_3[0].b); TT (sa_3[0].b, sa_3[0].a);	\
+      								\
+      TT (sa_3[0].a, sa_3[1].a); TT (sa_3[0].a, sa_3[1].b);	\
+      TT (sa_3[0].b, sa_3[1].a); TT (sa_3[0].b, sa_3[1].b);	\
+    								\
+      TT (sa_3[0].a, sa_3[2].a); TT (sa_3[0].a, sa_3[2].b);	\
+      TT (sa_3[0].b, sa_3[2].a); TT (sa_3[0].b, sa_3[2].b);	\
+								\
+      TT (sa_3[1].a, sa_3[0].a); TT (sa_3[1].a, sa_3[0].b);	\
+      TT (sa_3[1].b, sa_3[0].a); TT (sa_3[1].b, sa_3[0].b);	\
+								\
+      TT (sa_3[1].a, sa_3[1].b); TT (sa_3[1].b, sa_3[1].a);	\
+								\
+      TT (sa_3[1].a, sa_3[2].a); TT (sa_3[1].a, sa_3[2].b);	\
+      TT (sa_3[1].b, sa_3[2].a); TT (sa_3[1].b, sa_3[2].b);	\
+    } while (0)
+
+#define T2(i0, i1) do				\
+    {						\
+      enum { i2_0 = i0, i2_1 = i1 };		\
+      T1 (0); T1 (1); T1 (2);			\
+    } while (0)
+
+#define T3(i0, i1, i2) do			\
+    {						\
+      enum { i3_0 = i0, i3_1 = i1, i3_2 = i2 };	\
+      T2 (0, 0); T2 (0, 1); T2 (0, 2);		\
+      T2 (1, 0); T2 (1, 1); T2 (1, 2);		\
+      T2 (2, 0); T2 (2, 1); T2 (2, 2);		\
+    } while (0)
+
+#if 0
+  /* These tests take forever and a day to compile.  Enable them only
+     during the development of this feature but leave them otherwise
+     disabled to avoid slowing everything down for others.  */
+  T3 (0, 0, 0); T3 (0, 0, 1); T3 (0, 0, 2);
+  T3 (0, 1, 0); T3 (0, 1, 1); T3 (0, 1, 2);
+  T3 (0, 2, 0); T3 (0, 2, 1); T3 (0, 2, 2);
+
+  T3 (1, 0, 0); T3 (1, 0, 1); T3 (1, 0, 2);
+  T3 (1, 1, 0); T3 (1, 1, 1); T3 (1, 1, 2);
+  T3 (1, 2, 0); T3 (1, 2, 1); T3 (1, 2, 2);
+
+  T3 (2, 0, 0); T3 (2, 0, 1); T3 (2, 0, 2);
+  T3 (2, 1, 0); T3 (2, 1, 1); T3 (2, 1, 2);
+  T3 (2, 2, 0); T3 (2, 2, 1); T3 (2, 2, 2);
+#endif
+}
+
+void test_struct_member_flexarray (struct S3 *s3, int i, int j)
+{
+  char *d = s3->fa;
+  char *s = s3->fa;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 0);   /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &s[0]);   /* { dg-warning "overlaps" } */
+  T (d, "%s", &s[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", s);   /* { dg-warning "overlaps" } */
+  T (d + 1, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + i, "%s", s);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", s);   /* { dg-warning "overlaps" } */
+  T (&d[1], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[i], "%s", s);   /* { dg-warning "may overlap" } */
+
+  d = s3->fa + i;
+  s = s3->fa + j;
+
+  T (d, "%s", s);       /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 0);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &s[0]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &s[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + 1, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", s);   /* { dg-warning "may overlap" } */
+  T (d + j, "%s", s);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[1], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", s);   /* { dg-warning "may overlap" } */
+  T (&d[j], "%s", s);   /* { dg-warning "may overlap" } */
+}
+
+union U
+{
+  struct S2 s2_1;
+  struct S2 s2_2;
+};
+
+void test_union_member_array (union U *un, int i)
+{
+  char *d = un->s2_1.s_1.a;
+
+  T (d, "%s", d);       /* { dg-warning "overlaps" } */
+  T (d, "%s", d + 0);   /* { dg-warning "overlaps" } */
+  T (d, "%s", d + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", d + i);   /* { dg-warning "may overlap" } */
+
+  T (d, "%s", &d[0]);   /* { dg-warning "overlaps" } */
+  T (d, "%s", &d[1]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[2]);   /* { dg-warning "may overlap" } */
+  T (d, "%s", &d[i]);   /* { dg-warning "may overlap" } */
+
+  T (d + 0, "%s", d);   /* { dg-warning "overlaps" } */
+  T (d + 1, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%s", d);   /* { dg-warning "may overlap" } */
+  T (d + i, "%s", d);   /* { dg-warning "may overlap" } */
+
+  T (&d[0], "%s", d);   /* { dg-warning "overlaps" } */
+  T (&d[1], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[2], "%s", d);   /* { dg-warning "may overlap" } */
+  T (&d[i], "%s", d);   /* { dg-warning "may overlap" } */
+
+  const char *s = d;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  s = un->s2_1.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = un->s2_1.s_2.a;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = un->s2_1.s_2.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+
+  s = un->s2_2.s_1.a;
+
+  T (d, "%s", s);       /* { dg-warning "overlaps" } */
+  T (d, "%s", s + 1);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + 2);   /* { dg-warning "may overlap" } */
+  T (d, "%s", s + i);   /* { dg-warning "may overlap" } */
+
+  s = un->s2_2.s_1.b;
+
+  T (d, "%s", s);
+  T (d, "%s", s + 1);
+  T (d, "%s", s + 2);
+  T (d, "%s", s + i);
+}
+
+void test_multiple_overlap (int i)
+{
+  {
+    char a[3] = "";           /* { dg-message "declared here" } */
+
+    /* Both a3 and a4 definitely overlap the output even if the are
+       empty because of the terminating nul.  */
+    char *d = a;
+    char *a3 = a + 0;
+    char *a4 = a - 0;
+
+    T (d, "%s%s", a3, a4);    /* { dg-warning "arguments 3, 4 overlap destination object .a." }*/
+  }
+
+  {
+    char a[4];                /* { dg-message "declared here" } */
+
+    /* a3 and a4 may overlap the output.  They will only not overlap
+       it when a3 is empty, and a4 is at most chaeracter byte long.  */
+    char *d = a;
+    char *a3 = a + 2;
+    char *a4 = a + 3;
+
+    T (d, "%s%s", a3, a4);    /* { dg-warning "arguments 3, 4 may overlap destination object .a." }*/
+  }
+
+  {
+    char a[5];                /* { dg-message "declared here" } */
+
+    /* a3 certaibly overlaps the output, but a4 may or may not depending
+       in the value of i.  */
+    char *d = a;
+    char *a3 = a + 0;
+    char *a4 = a + i;
+
+    T (d, "%s%s", a3, a4);    /* { dg-warning "arguments 3 and maybe 4 overlap destination object .a." }*/
+  }
+}
+
+void test_overlap_with_precision (char *d, int i, int j)
+{
+  T (d, "%.0s", d + 0);       /* { dg-warning "overlaps" } */
+
+  /* The following doesn't overlap but the implementation doesn't
+     distinguish between a %s argument that's a zero-length string
+     (and must be nul-terminated) and one that results in no bytes
+     because of zero precision (and thus need not be nul-terminated).
+     This should be handled at some point.  */
+  T (d, "%.0s", d + 1);       /* { dg-bogus "overlap" "" { xfail *-*-* } } */
+  T (d, "%.0s", d + 2);
+  T (d, "%.0s", d + i);       /* { dg-warning "may overlap" } */
+
+  T (d, "%.1s", d + 0);       /* { dg-warning "overlaps" } */
+  /* Unlike the %.0s case the following deserves a warning because
+     when d[1] isn't zero, it will be overwritten by the terminating
+     nul added by sprintf.  */
+  T (d, "%.1s", d + 1);       /* { dg-warning "may overlap" } */
+  T (d, "%.1s", d + 2);
+  T (d, "%.1s", d + i);       /* { dg-warning "may overlap" } */
+
+  T (d + 1, "%.0s", d);
+  T (d + 2, "%.0s", d);
+
+  T (d + 1, "%.1s", d);       /* { dg-warning "may overlap" } */
+  T (d + 2, "%.1s", d);
+
+  T (d + 2, "%.1s", d + 1);   /* { dg-warning "may overlap" } */
+  T (d + 2, "%.1s", d + i);   /* { dg-warning "may overlap" } */
+
+  /* The following should be "overlaps" but tracking that the offset
+     is the same variable doesn't seem worth the effort.  */
+  T (d + i, "%.1s", d + i);   /* { dg-warning "overlap" } */
+
+  T (d + i, "%.1s", d + j);   /* { dg-warning "may overlap" } */
+}