diff mbox series

[WIP] add object access attributes (PR 83859)

Message ID 056e2b5b-696c-ca69-9027-7d2369354b07@gmail.com
State New
Headers show
Series [WIP] add object access attributes (PR 83859) | expand

Commit Message

Martin Sebor Sept. 29, 2019, 7:51 p.m. UTC
-Wstringop-overflow detects a subset of past-the-end read and write
accesses by built-in functions such as memcpy and strcpy.  It relies
on the functions' effects the knowledge of which is hardwired into
GCC.  Although it's possible for users to create wrappers for their
own functions to detect similar problems, it's quite cumbersome and
so only lightly used outside system libraries like Glibc.  Even Glibc
only checks for buffer overflow and not for reading past the end.

PR 83859 asks to expose the same checking that GCC does natively for
built-in calls via a function attribute that associates a pointer
argument with the size argument, such as:

   __attribute__((buffer_size (1, 2))) void
   f (char* dst, size_t dstsize);

The attached patch is my initial stab at providing this feature by
introducing three new attributes:

   * read_only (ptr-argno, size-argno)
   * read_only (ptr-argno, size-argno)
   * read_write (ptr-argno, size-argno)

As requested, the attributes associate a pointer parameter to
a function with a size parameter.  In addition, they also specify
how the function accesses the object the pointer points to: either
it only reads from it, or it only writes to it, or it does both.

Besides enabling the same buffer overflow detection as for built-in
string functions they also let GCC issue -Wuninitialized warnings
for uninitialized objects passed to read-only functions by reference,
and -Wunused-but-set warnings for objects passed to write-only
functions that are otherwise unused (PR 80806).  The -Wununitialized
part is done. The -Wunused-but-set detection is implemented only in
the C FE and not yet in C++.

Besides the diagnostic improvements above the attributes also open
up optimization opportunities such as DCE.  I'm still working on this
and so it's not yet part of the initial patch.

I plan to finish the patch for GCC 10 but I don't expect to have
the time to start taking advantage of the attributes for optimization
until GCC 11.

Besides regression testing on x86_64-linux, I also tested the patch
by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
found no new problems but caused a handful of -Wunused-but-set-variable 
false positives due to an outstanding bug in the C front-end introduced
by the patch that I still need to fix.

Martin

Comments

Richard Biener Sept. 30, 2019, 7:37 a.m. UTC | #1
On Sun, Sep 29, 2019 at 9:52 PM Martin Sebor <msebor@gmail.com> wrote:
>
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
>
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
>
>    __attribute__((buffer_size (1, 2))) void
>    f (char* dst, size_t dstsize);
>
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
>
>    * read_only (ptr-argno, size-argno)
>    * read_only (ptr-argno, size-argno)
>    * read_write (ptr-argno, size-argno)
>
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
>
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
>
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.

There's the "fn spec" attribute which you can use for the optimization
part.  Note "fn spec" also likes to know whether the address of the
argument escapes and whether the argument is only dereferenced
directly or also indirectly (when passing a pointer to a struct is
transitively reachable memory through the pointer accessed or not?).

So you should at least make sure to document the full
semantics of your proposed read_only/write_only/read_write atributes.

I guess that "read_only" means that direct accesses do not write
but the attribute does not constrain indirect accesses?

Note "fn spec" doesn't offer read/write constraints when not at the
same time constraining escaping since when a pointer escapes
through a call we cannot make any optimization for a positional
read/write constraint since a function can access global memory
(and all escaped pointed-to data ends up as "global memory").
There's no way to tell (via "fn spec") that the function only accesses
memory reachable via function arguments.

So I'm not sure there's a 1:1 mapping for your desired semantics
to "fn spec" plus your desired semantics may offer no
opportunity for optimization.  Useful would be if read_only
would map to "R" and read_write and write_only would map to "W".

Richard.

> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
>
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
>
> Martin
Martin Sebor Sept. 30, 2019, 3:41 p.m. UTC | #2
On 9/30/19 1:37 AM, Richard Biener wrote:
> On Sun, Sep 29, 2019 at 9:52 PM Martin Sebor <msebor@gmail.com> wrote:
>>
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>     __attribute__((buffer_size (1, 2))) void
>>     f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>     * read_only (ptr-argno, size-argno)
>>     * read_only (ptr-argno, size-argno)
>>     * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
> 
> There's the "fn spec" attribute which you can use for the optimization
> part.  Note "fn spec" also likes to know whether the address of the
> argument escapes and whether the argument is only dereferenced
> directly or also indirectly (when passing a pointer to a struct is
> transitively reachable memory through the pointer accessed or not?).

Thanks, I'll look into those.

> 
> So you should at least make sure to document the full
> semantics of your proposed read_only/write_only/read_write atributes.
> 
> I guess that "read_only" means that direct accesses do not write
> but the attribute does not constrain indirect accesses?

Correct.  Some other annotation is necessary to constrain those.
A read-only restrict-qualified pointer would do that.  Because
the read-only attribute only applies to const pointers the only
purpose it serves in that combination is the association with
the size parameter.  As in:

   __attribute__ ((read_only (2, 3))) void*
   memcpy (void* restrict, const void *restrict, size_t);

without the pointer-size association the above reduces to this:

   __attribute__ ((read_only)) void*
   memcpy (void* restrict, const void *restrict, size_t);

which should ultimately have the same effect as the plain

   void*
   memcpy (void* restrict, const void *restrict, size_t);

As a future extension (GCC 11 or beyond) I'd like to look into
allowing the read/write attributes on object and mainly subobject
declarations.  There too, read-only should be paired with restrict
to express the same constraint.

> Note "fn spec" doesn't offer read/write constraints when not at the
> same time constraining escaping since when a pointer escapes
> through a call we cannot make any optimization for a positional
> read/write constraint since a function can access global memory
> (and all escaped pointed-to data ends up as "global memory").
> There's no way to tell (via "fn spec") that the function only accesses
> memory reachable via function arguments.

In my WIP patch I have a no_side_effect attribute that further
constrains what a function can do.  It's just like pure except
that it lets the function access objects passed to it by reference.

> 
> So I'm not sure there's a 1:1 mapping for your desired semantics
> to "fn spec" plus your desired semantics may offer no
> opportunity for optimization.  Useful would be if read_only
> would map to "R" and read_write and write_only would map to "W".

I didn't know about the R and W fnspecs.  Let me look into them
for GCC 11 to see if I can make use of them for the optimization.

Martin

> 
> Richard.
> 
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of -Wunused-but-set-variable
>> false positives due to an outstanding bug in the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin
Joseph Myers Sept. 30, 2019, 9:34 p.m. UTC | #3
On Sun, 29 Sep 2019, Martin Sebor wrote:

> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:

I'll also note that, as mentioned in that bug (but more specifically 
covered by the separate bug 50584) it would make sense to have similar 
warnings with [static] parameter array declarators.  And that the C2x 
charter includes a principle that new interfaces should have the array 
size before the array parameter to allow parameters to be declared using 
VLA syntax like that, so it's plausible the [static] case will be useful 
for more functions in future (although it's up to library implementations 
exactly what form they use in their headers, given they have other 
considerations such as C++ compatibility).

Hopefully the infrastructure in this patch will facilitate future support 
for such diagnostics in the [static] case.
Martin Sebor Oct. 1, 2019, 2:36 a.m. UTC | #4
On 9/30/19 3:34 PM, Joseph Myers wrote:
> On Sun, 29 Sep 2019, Martin Sebor wrote:
> 
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
> 
> I'll also note that, as mentioned in that bug (but more specifically
> covered by the separate bug 50584) it would make sense to have similar
> warnings with [static] parameter array declarators.  And that the C2x
> charter includes a principle that new interfaces should have the array
> size before the array parameter to allow parameters to be declared using
> VLA syntax like that, so it's plausible the [static] case will be useful
> for more functions in future (although it's up to library implementations
> exactly what form they use in their headers, given they have other
> considerations such as C++ compatibility).
> 
> Hopefully the infrastructure in this patch will facilitate future support
> for such diagnostics in the [static] case.

Thanks for the reminder!  I will make sure the infrastructure makes
the VLA checking possible, even if it's not actually implemented for
GCC 10.

Martin
Martin Sebor Oct. 17, 2019, 4:28 p.m. UTC | #5
Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html

Other than the suggestions I got for optimization (for GCC 11)
and additional buffer overflow detection for [static] arrays),
is there any feedback on the patch itself?  Jeff?

Martin

On 9/29/19 1:51 PM, Martin Sebor wrote:
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
> 
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
> 
>    __attribute__((buffer_size (1, 2))) void
>    f (char* dst, size_t dstsize);
> 
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
> 
>    * read_only (ptr-argno, size-argno)
>    * read_only (ptr-argno, size-argno)
>    * read_write (ptr-argno, size-argno)
> 
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
> 
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
> 
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.
> 
> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
> 
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable 
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
> 
> Martin
Martin Sebor Oct. 24, 2019, 2:39 p.m. UTC | #6
Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html

On 10/17/2019 10:28 AM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html
> 
> Other than the suggestions I got for optimization (for GCC 11)
> and additional buffer overflow detection for [static] arrays),
> is there any feedback on the patch itself?  Jeff?
> 
> Martin
> 
> On 9/29/19 1:51 PM, Martin Sebor wrote:
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>    __attribute__((buffer_size (1, 2))) void
>>    f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>    * read_only (ptr-argno, size-argno)
>>    * read_only (ptr-argno, size-argno)
>>    * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
>>
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of 
>> -Wunused-but-set-variable false positives due to an outstanding bug in 
>> the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin
>
Jeff Law Oct. 27, 2019, 5:31 p.m. UTC | #7
On 9/29/19 1:51 PM, Martin Sebor wrote:
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
> 
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
> 
>   __attribute__((buffer_size (1, 2))) void
>   f (char* dst, size_t dstsize);
> 
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
> 
>   * read_only (ptr-argno, size-argno)
>   * read_only (ptr-argno, size-argno)
>   * read_write (ptr-argno, size-argno)
> 
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
> 
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
> 
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.
> 
> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
> 
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
> 
> Martin
> 
> gcc-80806.diff
> 
> PR c/80806 - gcc does not warn if local array is memset only
> PR middle-end/83859 - attribute to associate buffer and its size
> 
> gcc/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
> 	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
> 	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
> 	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
> 	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
> 	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
> 	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
> 	* builtins.c (check_access): Make extern.  Consistently set
> 	the no-warning bit after issuing a warning.
> 	* builtins.h (check_access): Declare.
> 	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
> 	read_only and write_only attributes.
> 	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
> 	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
> 	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
> 	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
> 	(__strcpy_chk, __strncpy_chk): Same.
> 	* calls.c (rdwr_access_hash): New type.
> 	(rdwr_map): Same.
> 	(init_attr_rdwr_indices): New function.
> 	(maybe_warn_rdwr_sizes): Same.
> 	(initialize_argument_information): Call init_attr_rdwr_indices.
> 	Call maybe_warn_rdwr_sizes.
> 	* doc/extend.texi (no_side_effect): Document new attribute.
> 	(read_only, write_only, read_write): Same.
> 	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
> 	(warn_uninitialized_vars): Rename argument.  Factor out code into
> 	maybe_warn_uninit_accesss.  Call it.
> 
> gcc/c/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
> 	(c_parser_expr_list): Add argument.
> 	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
> 	(c_parser_initializer): Set parser->in_init.
> 	(c_parser_binary_expression): Use parser->no_set_read.
> 	(c_parser_unary_expression): Same.
> 	(c_parser_sizeof_expression): Use parser->in_arg.
> 	(c_parser_postfix_expression_after_primary): Adjust.
> 	(is_write_only_p): New function.
> 	(c_parser_expr_list): Add argument.
> 	Avoid setting DECL_READ_P for decls passed to write-only function
> 	arguments.
> 	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
> 	(c_parser_oacc_wait_list): Same.
> 	* c-tree.h (parser_build_binary_op): Add argument.
> 	* c-typeck.c (default_conversion): Same.  Use it.
> 	(parser_build_binary_op): Same.
> 	(build_binary_op): Same.
> 
> gcc/c-family/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-attribs.c (handle_no_side_effect_attribute): New function.
> 	(handle_read_only_attribute, handle_write_only_attribute): Same.
> 	(handle_read_write_attribute): Same.
> 	(c_common_attribute_table): Add new attributes.
> 	(get_argument_type): New function.
> 	(handle_rdwr_attributes): Same.
> 	(has_attribute): Add argument to callback signature.  Pass to it
> 	a new value.
> 	(get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 	* c-common.c (get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 	(build_binary_op): Add a new argument.
> 	(default_conversion): Same.
> 	(has_attribute): Adjust argument type.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* typeck.c (cp_default_conversion): Add argument.
> 	(build_binary_op): Same.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
> 	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> 	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
> 	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
> 	* gcc.dg/attr-alloc_size.c: Adjust.
> 	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
> 	* gcc.dg/nonnull-3.c: Adjust.
> 	* gcc.dg/pr40340-2.c: Adjust.
> 	* gcc.dg/pr78768.c: Adjust.
> 	* gcc.dg/pr79715.c: Adjust.
> 	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
> 	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
You mention that this is almost done, but not yet finished.  If I were
working on this I could have split this up into adding the support for
the attributes first without trying to use them anywhere first.  Then a
separate patch that adds the attribute to the builtins, then a patch
that exploited the new attributes to do something useful.

Please try to avoid creating large patches that are so easily broken up.
 It just makes review harder than it needs to be and slows down getting
the patches integrated.

Your ChangeLog references "no_side_effect" in a few places.  But I don't
see any of those changes actually in the patch, except for adding it in
builtin-attrs.def.  I'm guessing those are supposed to all be part of a
follow-up patch?

FWIW, I believe there may be a DSE case that your patches allow us to
handle.  Essentially we had a case where DSE didn't fire because we
didn't have detailed information about how the operands to one of the
mem* or str* calls was used.  I don't expect you to own this missing opt
as part of this submission.


> diff --git a/gcc/attribs.h b/gcc/attribs.h
> index 23a7321e04a..0e2320701b1 100644
> --- a/gcc/attribs.h
> +++ b/gcc/attribs.h
> @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
>      }
>  }
>  
> +/* Description of a function argument declared attribute read_only,
> +   read_write, or write_only.  Used as an "iterator" over all such
> +   arguments in a function declaration or call.  */
> +
> +struct attr_access
> +{
> +  /* Attribute chain for the given function declaration.  */
> +  tree attrs;
> +
> +  /* The attribute pointer argument.  */
> +  tree ptr;
> +  /* The size of the pointed-to object or NULL when not specified.  */
> +  tree size;
> +
> +  /* The tree node corresponding to the current argument in the chain
> +     of formal function arguments in a call to the given function.
> +     Used by attributes that specify that relevant arguments are of
> +     the given kind, as in
> +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
> +  tree argchain;
> +
> +  /* The zero-based number of each of the formal function arguments.  */
> +  unsigned ptrarg;
> +  unsigned sizarg;
> +  enum Kind { read_only, write_only, read_write };
Nit: Mixed case "Kind" use either KIND or kind.





> diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
> index d4e12eb93d1..2e439c43b91 100644
> --- a/gcc/c/c-typeck.c
> +++ b/gcc/c/c-typeck.c
> @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
>     In addition, manifest constants symbols are replaced by their values.  */
>  
>  tree
> -default_conversion (tree exp)
> +default_conversion (tree exp, bool read_p /* = true */)
Please update the function comment to describe the new argument.  I
think you need to do that for c_parser_expr_list and
parser_build_binary_op and build_binary_op.


> diff --git a/gcc/gimple.h b/gcc/gimple.h
> index cf1f8da5ae2..4d850a088cf 100644
> --- a/gcc/gimple.h
> +++ b/gcc/gimple.h
> @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
>  
>  #include "tree-ssa-alias.h"
>  #include "gimple-expr.h"
> +#include "function.h"
> +#include "basic-block.h"
How important is this (ie, how painful is it to have the .c/.cc files
include function.h/basic-block.h?  We generally frown on having one
header include others like this.



> diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
> index fe8f8f0bc28..b9d455159cb 100644
> --- a/gcc/tree-ssa-uninit.c
> +++ b/gcc/tree-ssa-uninit.c
Something else to consider (as a separate follow-up).

IIUC your code adds checking arguments at call sites which is a nice
improvement.  There may be BZs related to this issue.

Another concept we might want to consider based on what I've seen pop up
fairly often in code is the concept of "must write".  In the caller we'd
consider passing a must-write object to a function call as
initialization which would cut down on false positives.  We could verify
the behavior in the callee.

Also, I think Fortran has the concept of "Intent" which does largely the
same thing you're doing.  You might consider reaching out to the Fortran
front-end folks and see if they can encode their Intent information into
your attributes.  I believe there's BZs related to using Intent
information to avoid false positives from the uninit warning pass.

I don't see anything terribly concerning.  Looking forward to the final
iteration here.

jeff
Richard Biener Oct. 28, 2019, 10:14 a.m. UTC | #8
On Sun, Oct 27, 2019 at 6:32 PM Jeff Law <law@redhat.com> wrote:
>
> On 9/29/19 1:51 PM, Martin Sebor wrote:
> > -Wstringop-overflow detects a subset of past-the-end read and write
> > accesses by built-in functions such as memcpy and strcpy.  It relies
> > on the functions' effects the knowledge of which is hardwired into
> > GCC.  Although it's possible for users to create wrappers for their
> > own functions to detect similar problems, it's quite cumbersome and
> > so only lightly used outside system libraries like Glibc.  Even Glibc
> > only checks for buffer overflow and not for reading past the end.
> >
> > PR 83859 asks to expose the same checking that GCC does natively for
> > built-in calls via a function attribute that associates a pointer
> > argument with the size argument, such as:
> >
> >   __attribute__((buffer_size (1, 2))) void
> >   f (char* dst, size_t dstsize);
> >
> > The attached patch is my initial stab at providing this feature by
> > introducing three new attributes:
> >
> >   * read_only (ptr-argno, size-argno)
> >   * read_only (ptr-argno, size-argno)
> >   * read_write (ptr-argno, size-argno)
> >
> > As requested, the attributes associate a pointer parameter to
> > a function with a size parameter.  In addition, they also specify
> > how the function accesses the object the pointer points to: either
> > it only reads from it, or it only writes to it, or it does both.
> >
> > Besides enabling the same buffer overflow detection as for built-in
> > string functions they also let GCC issue -Wuninitialized warnings
> > for uninitialized objects passed to read-only functions by reference,
> > and -Wunused-but-set warnings for objects passed to write-only
> > functions that are otherwise unused (PR 80806).  The -Wununitialized
> > part is done. The -Wunused-but-set detection is implemented only in
> > the C FE and not yet in C++.
> >
> > Besides the diagnostic improvements above the attributes also open
> > up optimization opportunities such as DCE.  I'm still working on this
> > and so it's not yet part of the initial patch.
> >
> > I plan to finish the patch for GCC 10 but I don't expect to have
> > the time to start taking advantage of the attributes for optimization
> > until GCC 11.
> >
> > Besides regression testing on x86_64-linux, I also tested the patch
> > by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> > found no new problems but caused a handful of -Wunused-but-set-variable
> > false positives due to an outstanding bug in the C front-end introduced
> > by the patch that I still need to fix.
> >
> > Martin
> >
> > gcc-80806.diff
> >
> > PR c/80806 - gcc does not warn if local array is memset only
> > PR middle-end/83859 - attribute to associate buffer and its size
> >
> > gcc/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
> >       (ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
> >       (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
> >       (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
> >       (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
> >       (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
> >       (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
> >       * builtins.c (check_access): Make extern.  Consistently set
> >       the no-warning bit after issuing a warning.
> >       * builtins.h (check_access): Declare.
> >       * builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
> >       read_only and write_only attributes.
> >       (memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
> >       (strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
> >       (strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
> >       (free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
> >       (__strcpy_chk, __strncpy_chk): Same.
> >       * calls.c (rdwr_access_hash): New type.
> >       (rdwr_map): Same.
> >       (init_attr_rdwr_indices): New function.
> >       (maybe_warn_rdwr_sizes): Same.
> >       (initialize_argument_information): Call init_attr_rdwr_indices.
> >       Call maybe_warn_rdwr_sizes.
> >       * doc/extend.texi (no_side_effect): Document new attribute.
> >       (read_only, write_only, read_write): Same.
> >       * tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
> >       (warn_uninitialized_vars): Rename argument.  Factor out code into
> >       maybe_warn_uninit_accesss.  Call it.
> >
> > gcc/c/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
> >       (c_parser_expr_list): Add argument.
> >       (c_parser_attribute): Pass a new argument to get_nonnull_operand.
> >       (c_parser_initializer): Set parser->in_init.
> >       (c_parser_binary_expression): Use parser->no_set_read.
> >       (c_parser_unary_expression): Same.
> >       (c_parser_sizeof_expression): Use parser->in_arg.
> >       (c_parser_postfix_expression_after_primary): Adjust.
> >       (is_write_only_p): New function.
> >       (c_parser_expr_list): Add argument.
> >       Avoid setting DECL_READ_P for decls passed to write-only function
> >       arguments.
> >       (c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
> >       (c_parser_oacc_wait_list): Same.
> >       * c-tree.h (parser_build_binary_op): Add argument.
> >       * c-typeck.c (default_conversion): Same.  Use it.
> >       (parser_build_binary_op): Same.
> >       (build_binary_op): Same.
> >
> > gcc/c-family/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-attribs.c (handle_no_side_effect_attribute): New function.
> >       (handle_read_only_attribute, handle_write_only_attribute): Same.
> >       (handle_read_write_attribute): Same.
> >       (c_common_attribute_table): Add new attributes.
> >       (get_argument_type): New function.
> >       (handle_rdwr_attributes): Same.
> >       (has_attribute): Add argument to callback signature.  Pass to it
> >       a new value.
> >       (get_nonnull_operand): Rename...
> >       (get_attribute_operand): ...to this.
> >       * c-common.c (get_nonnull_operand): Rename...
> >       (get_attribute_operand): ...to this.
> >       (build_binary_op): Add a new argument.
> >       (default_conversion): Same.
> >       (has_attribute): Adjust argument type.
> >
> > gcc/cp/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * typeck.c (cp_default_conversion): Add argument.
> >       (build_binary_op): Same.
> >
> > gcc/testsuite/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
> >       * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> >       * gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
> >       * gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
> >       * gcc.dg/attr-alloc_size.c: Adjust.
> >       * gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-read-only.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-write-only.c: New test.
> >       * gcc.dg/nonnull-3.c: Adjust.
> >       * gcc.dg/pr40340-2.c: Adjust.
> >       * gcc.dg/pr78768.c: Adjust.
> >       * gcc.dg/pr79715.c: Adjust.
> >       * gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
> >       * gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
> You mention that this is almost done, but not yet finished.  If I were
> working on this I could have split this up into adding the support for
> the attributes first without trying to use them anywhere first.  Then a
> separate patch that adds the attribute to the builtins, then a patch
> that exploited the new attributes to do something useful.
>
> Please try to avoid creating large patches that are so easily broken up.
>  It just makes review harder than it needs to be and slows down getting
> the patches integrated.
>
> Your ChangeLog references "no_side_effect" in a few places.  But I don't
> see any of those changes actually in the patch, except for adding it in
> builtin-attrs.def.  I'm guessing those are supposed to all be part of a
> follow-up patch?
>
> FWIW, I believe there may be a DSE case that your patches allow us to
> handle.  Essentially we had a case where DSE didn't fire because we
> didn't have detailed information about how the operands to one of the
> mem* or str* calls was used.  I don't expect you to own this missing opt
> as part of this submission.
>
>
> > diff --git a/gcc/attribs.h b/gcc/attribs.h
> > index 23a7321e04a..0e2320701b1 100644
> > --- a/gcc/attribs.h
> > +++ b/gcc/attribs.h
> > @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
> >      }
> >  }
> >
> > +/* Description of a function argument declared attribute read_only,
> > +   read_write, or write_only.  Used as an "iterator" over all such
> > +   arguments in a function declaration or call.  */
> > +
> > +struct attr_access
> > +{
> > +  /* Attribute chain for the given function declaration.  */
> > +  tree attrs;
> > +
> > +  /* The attribute pointer argument.  */
> > +  tree ptr;
> > +  /* The size of the pointed-to object or NULL when not specified.  */
> > +  tree size;
> > +
> > +  /* The tree node corresponding to the current argument in the chain
> > +     of formal function arguments in a call to the given function.
> > +     Used by attributes that specify that relevant arguments are of
> > +     the given kind, as in
> > +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
> > +  tree argchain;
> > +
> > +  /* The zero-based number of each of the formal function arguments.  */
> > +  unsigned ptrarg;
> > +  unsigned sizarg;
> > +  enum Kind { read_only, write_only, read_write };
> Nit: Mixed case "Kind" use either KIND or kind.
>
>
>
>
>
> > diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
> > index d4e12eb93d1..2e439c43b91 100644
> > --- a/gcc/c/c-typeck.c
> > +++ b/gcc/c/c-typeck.c
> > @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
> >     In addition, manifest constants symbols are replaced by their values.  */
> >
> >  tree
> > -default_conversion (tree exp)
> > +default_conversion (tree exp, bool read_p /* = true */)
> Please update the function comment to describe the new argument.  I
> think you need to do that for c_parser_expr_list and
> parser_build_binary_op and build_binary_op.
>
>
> > diff --git a/gcc/gimple.h b/gcc/gimple.h
> > index cf1f8da5ae2..4d850a088cf 100644
> > --- a/gcc/gimple.h
> > +++ b/gcc/gimple.h
> > @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
> >
> >  #include "tree-ssa-alias.h"
> >  #include "gimple-expr.h"
> > +#include "function.h"
> > +#include "basic-block.h"
> How important is this (ie, how painful is it to have the .c/.cc files
> include function.h/basic-block.h?  We generally frown on having one
> header include others like this.
>
>
>
> > diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
> > index fe8f8f0bc28..b9d455159cb 100644
> > --- a/gcc/tree-ssa-uninit.c
> > +++ b/gcc/tree-ssa-uninit.c
> Something else to consider (as a separate follow-up).
>
> IIUC your code adds checking arguments at call sites which is a nice
> improvement.  There may be BZs related to this issue.
>
> Another concept we might want to consider based on what I've seen pop up
> fairly often in code is the concept of "must write".  In the caller we'd
> consider passing a must-write object to a function call as
> initialization which would cut down on false positives.  We could verify
> the behavior in the callee.
>
> Also, I think Fortran has the concept of "Intent" which does largely the
> same thing you're doing.  You might consider reaching out to the Fortran
> front-end folks and see if they can encode their Intent information into
> your attributes.  I believe there's BZs related to using Intent
> information to avoid false positives from the uninit warning pass.
>
> I don't see anything terribly concerning.  Looking forward to the final
> iteration here.

Note I've chickened out to exposing these kind of annotations to users
with 'fn spec' which provides fortran Intent(IN/OUT) semantics to optimizers.
I think we need to formally define what those attributes guarantee for
optimizations first and arrive at consensus here.

Once we have those attributes we have to support whatever broken things
we did.

So I'd rather have them in the implementation namespace for now only.

Richard.

> jeff
>
Martin Sebor Nov. 15, 2019, 9:28 p.m. UTC | #9
On 10/27/19 11:31 AM, Jeff Law wrote:
> On 9/29/19 1:51 PM, Martin Sebor wrote:
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>    __attribute__((buffer_size (1, 2))) void
>>    f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>    * read_only (ptr-argno, size-argno)
>>    * read_only (ptr-argno, size-argno)
>>    * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
>>
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of -Wunused-but-set-variable
>> false positives due to an outstanding bug in the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin
>>
>> gcc-80806.diff
>>
>> PR c/80806 - gcc does not warn if local array is memset only
>> PR middle-end/83859 - attribute to associate buffer and its size
>>
>> gcc/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
>> 	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
>> 	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
>> 	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
>> 	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
>> 	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
>> 	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
>> 	* builtins.c (check_access): Make extern.  Consistently set
>> 	the no-warning bit after issuing a warning.
>> 	* builtins.h (check_access): Declare.
>> 	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
>> 	read_only and write_only attributes.
>> 	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
>> 	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
>> 	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
>> 	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
>> 	(__strcpy_chk, __strncpy_chk): Same.
>> 	* calls.c (rdwr_access_hash): New type.
>> 	(rdwr_map): Same.
>> 	(init_attr_rdwr_indices): New function.
>> 	(maybe_warn_rdwr_sizes): Same.
>> 	(initialize_argument_information): Call init_attr_rdwr_indices.
>> 	Call maybe_warn_rdwr_sizes.
>> 	* doc/extend.texi (no_side_effect): Document new attribute.
>> 	(read_only, write_only, read_write): Same.
>> 	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
>> 	(warn_uninitialized_vars): Rename argument.  Factor out code into
>> 	maybe_warn_uninit_accesss.  Call it.
>>
>> gcc/c/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
>> 	(c_parser_expr_list): Add argument.
>> 	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
>> 	(c_parser_initializer): Set parser->in_init.
>> 	(c_parser_binary_expression): Use parser->no_set_read.
>> 	(c_parser_unary_expression): Same.
>> 	(c_parser_sizeof_expression): Use parser->in_arg.
>> 	(c_parser_postfix_expression_after_primary): Adjust.
>> 	(is_write_only_p): New function.
>> 	(c_parser_expr_list): Add argument.
>> 	Avoid setting DECL_READ_P for decls passed to write-only function
>> 	arguments.
>> 	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
>> 	(c_parser_oacc_wait_list): Same.
>> 	* c-tree.h (parser_build_binary_op): Add argument.
>> 	* c-typeck.c (default_conversion): Same.  Use it.
>> 	(parser_build_binary_op): Same.
>> 	(build_binary_op): Same.
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-attribs.c (handle_no_side_effect_attribute): New function.
>> 	(handle_read_only_attribute, handle_write_only_attribute): Same.
>> 	(handle_read_write_attribute): Same.
>> 	(c_common_attribute_table): Add new attributes.
>> 	(get_argument_type): New function.
>> 	(handle_rdwr_attributes): Same.
>> 	(has_attribute): Add argument to callback signature.  Pass to it
>> 	a new value.
>> 	(get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>> 	* c-common.c (get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>> 	(build_binary_op): Add a new argument.
>> 	(default_conversion): Same.
>> 	(has_attribute): Adjust argument type.
>>
>> gcc/cp/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* typeck.c (cp_default_conversion): Add argument.
>> 	(build_binary_op): Same.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
>> 	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
>> 	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
>> 	* gcc.dg/attr-alloc_size.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
>> 	* gcc.dg/nonnull-3.c: Adjust.
>> 	* gcc.dg/pr40340-2.c: Adjust.
>> 	* gcc.dg/pr78768.c: Adjust.
>> 	* gcc.dg/pr79715.c: Adjust.
>> 	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
> You mention that this is almost done, but not yet finished.

It was not finished in the sense that I knew of a false positive
warning due to the C front-end change.  (I was also looking for
feedback on some of the design and implementation choices.)

   If I were
> working on this I could have split this up into adding the support for
> the attributes first without trying to use them anywhere first.  Then a
> separate patch that adds the attribute to the builtins, then a patch
> that exploited the new attributes to do something useful.
> 
> Please try to avoid creating large patches that are so easily broken up.
>   It just makes review harder than it needs to be and slows down getting
> the patches integrated.

I couldn't think of a way to test the new attributes without also
making other changes, but I've come up with one so I extracted just
the basic bits from it and I'm submitting those now.  I'll post
the rest as I wrap them up.

> Your ChangeLog references "no_side_effect" in a few places.  But I don't
> see any of those changes actually in the patch, except for adding it in
> builtin-attrs.def.  I'm guessing those are supposed to all be part of a
> follow-up patch?

Yes, no_side_effect is part of the optimization changes.  I have
removed mentions of it in the attached revision.

> 
> FWIW, I believe there may be a DSE case that your patches allow us to
> handle.  Essentially we had a case where DSE didn't fire because we
> didn't have detailed information about how the operands to one of the
> mem* or str* calls was used.  I don't expect you to own this missing opt
> as part of this submission.

That's right.  This is something I'd like to tackle for GCC 11.

> 
> 
>> diff --git a/gcc/attribs.h b/gcc/attribs.h
>> index 23a7321e04a..0e2320701b1 100644
>> --- a/gcc/attribs.h
>> +++ b/gcc/attribs.h
>> @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
>>       }
>>   }
>>   
>> +/* Description of a function argument declared attribute read_only,
>> +   read_write, or write_only.  Used as an "iterator" over all such
>> +   arguments in a function declaration or call.  */
>> +
>> +struct attr_access
>> +{
>> +  /* Attribute chain for the given function declaration.  */
>> +  tree attrs;
>> +
>> +  /* The attribute pointer argument.  */
>> +  tree ptr;
>> +  /* The size of the pointed-to object or NULL when not specified.  */
>> +  tree size;
>> +
>> +  /* The tree node corresponding to the current argument in the chain
>> +     of formal function arguments in a call to the given function.
>> +     Used by attributes that specify that relevant arguments are of
>> +     the given kind, as in
>> +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
>> +  tree argchain;
>> +
>> +  /* The zero-based number of each of the formal function arguments.  */
>> +  unsigned ptrarg;
>> +  unsigned sizarg;
>> +  enum Kind { read_only, write_only, read_write };
> Nit: Mixed case "Kind" use either KIND or kind.

FWIW: I find either alternative makes the code harder, not easier
to read, and neither would make the code fully conform to the GNU
style (it asks to capitalize enum constants).  I've changed it
to "kind" (though I think it's fine either way and wish we would
dwell less on these things).

>> diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
>> index d4e12eb93d1..2e439c43b91 100644
>> --- a/gcc/c/c-typeck.c
>> +++ b/gcc/c/c-typeck.c
>> @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
>>      In addition, manifest constants symbols are replaced by their values.  */
>>   
>>   tree
>> -default_conversion (tree exp)
>> +default_conversion (tree exp, bool read_p /* = true */)
> Please update the function comment to describe the new argument.  I
> think you need to do that for c_parser_expr_list and
> parser_build_binary_op and build_binary_op.
> 
> 
>> diff --git a/gcc/gimple.h b/gcc/gimple.h
>> index cf1f8da5ae2..4d850a088cf 100644
>> --- a/gcc/gimple.h
>> +++ b/gcc/gimple.h
>> @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
>>   
>>   #include "tree-ssa-alias.h"
>>   #include "gimple-expr.h"
>> +#include "function.h"
>> +#include "basic-block.h"
> How important is this (ie, how painful is it to have the .c/.cc files
> include function.h/basic-block.h?  We generally frown on having one
> header include others like this.

The header already has a few #include directives presumably to
allow the inline functions compile.  I get compilation errors
without the #include directives and haven't tried to solve them
differently.  I know you agree but I can't help but this as
an opportunity to vent: it's really a horrible practice not to
have headers stand on their own.  It causes so much frustration
whenever a header is included in a .c file that doesn't already
include all the prerequisite headers.  There's got to be a tool
out there that would fix this mess automatically for us.

>> diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
>> index fe8f8f0bc28..b9d455159cb 100644
>> --- a/gcc/tree-ssa-uninit.c
>> +++ b/gcc/tree-ssa-uninit.c
> Something else to consider (as a separate follow-up).
> 
> IIUC your code adds checking arguments at call sites which is a nice
> improvement.  There may be BZs related to this issue.
> 
> Another concept we might want to consider based on what I've seen pop up
> fairly often in code is the concept of "must write".  In the caller we'd
> consider passing a must-write object to a function call as
> initialization which would cut down on false positives.  We could verify
> the behavior in the callee.

This is pretty much what the write_only attribute does.  For
the purposes of -Wuninitialized, at the call site, the compiler
assumes that the object has been written to.  (I've removed this
from the revised patch.)  There is nothing to enforce that
the function actually does write into the object.  That could
be added when the -Wuninitialized bits are added back, or later
on.

> 
> Also, I think Fortran has the concept of "Intent" which does largely the
> same thing you're doing.  You might consider reaching out to the Fortran
> front-end folks and see if they can encode their Intent information into
> your attributes.  I believe there's BZs related to using Intent
> information to avoid false positives from the uninit warning pass.

Thanks for the suggestion.  I will do that for GCC 11.  I take
Richard's point that the attributes' semantics need to be clearly
and carefully specified before they're put to use for optimization.

> 
> I don't see anything terribly concerning.  Looking forward to the final
> iteration here.

Attached is a subset of the original patch that just adds the three
attributes and uses them to do buffer overflow checking.  I have
also enhanced the detection of invalid arguments (null pointers,
negative sizes).

Retested on x86_64-linux.

Martin
Richard Biener Nov. 18, 2019, 8:36 a.m. UTC | #10
On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
>
> Thanks for the suggestion.  I will do that for GCC 11.  I take
> Richard's point that the attributes' semantics need to be clearly
> and carefully specified before they're put to use for optimization.

Before they are exposed to users please.  It doesn't help if we
specify the same attribute for optimization later when uses are out
in the wild "guessing" at what the possible interpretation is.

Maybe we can name your attributes maybe_readonly and friends
to clearly indicate that this is only a guess by the user so at most
usable for diagnostics but never for optimization.

Since we have quite costly attribute lookup I also prefer something
that translates to less attributes - how about
__attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
then merge this into a single diag_arspec attribute instance we can
lookup.

> >
> > I don't see anything terribly concerning.  Looking forward to the final
> > iteration here.
>
> Attached is a subset of the original patch that just adds the three
> attributes and uses them to do buffer overflow checking.  I have
> also enhanced the detection of invalid arguments (null pointers,
> negative sizes).
>
> Retested on x86_64-linux.
>
> Martin
Martin Sebor Nov. 18, 2019, 4:44 p.m. UTC | #11
On 11/18/19 1:36 AM, Richard Biener wrote:
> On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
>>
>> Thanks for the suggestion.  I will do that for GCC 11.  I take
>> Richard's point that the attributes' semantics need to be clearly
>> and carefully specified before they're put to use for optimization.
> 
> Before they are exposed to users please.  It doesn't help if we
> specify the same attribute for optimization later when uses are out
> in the wild "guessing" at what the possible interpretation is.
> 
> Maybe we can name your attributes maybe_readonly and friends
> to clearly indicate that this is only a guess by the user so at most
> usable for diagnostics but never for optimization.
> 
> Since we have quite costly attribute lookup I also prefer something
> that translates to less attributes - how about
> __attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
> to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
> then merge this into a single diag_arspec attribute instance we can
> lookup.

I can look into making a change along these lines.

I'm not fond of the idea of introducing a "maybe" kind of attributes
now and another parallel "for-sure" set later.  My goal is to have
the attributes express the same access constraints as those on
the arguments to built-in string functions like memcpy or strcat
(i.e., read_only only reads a pointed-to object, write_only only
writes, and, for strcat, read_write both reads and writes it).

Those properties are sufficiently well understood.  The three
attributes aren't intended to express constraints on aliasing
or on the side-effects of the functions, like restrict or
the const and pure attributes.

To let users do more than that, some additional annotation will
probably be necessary.  In my WIP patch I have a no_side_effect
attribute that lets functions do more than const and pure but
that's still work in progress that I don't plan to submit for
GCC 10.

Martin

> 
>>>
>>> I don't see anything terribly concerning.  Looking forward to the final
>>> iteration here.
>>
>> Attached is a subset of the original patch that just adds the three
>> attributes and uses them to do buffer overflow checking.  I have
>> also enhanced the detection of invalid arguments (null pointers,
>> negative sizes).
>>
>> Retested on x86_64-linux.
>>
>> Martin
Richard Biener Nov. 19, 2019, 8:51 a.m. UTC | #12
On Mon, Nov 18, 2019 at 5:45 PM Martin Sebor <msebor@gmail.com> wrote:
>
> On 11/18/19 1:36 AM, Richard Biener wrote:
> > On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
> >>
> >> Thanks for the suggestion.  I will do that for GCC 11.  I take
> >> Richard's point that the attributes' semantics need to be clearly
> >> and carefully specified before they're put to use for optimization.
> >
> > Before they are exposed to users please.  It doesn't help if we
> > specify the same attribute for optimization later when uses are out
> > in the wild "guessing" at what the possible interpretation is.
> >
> > Maybe we can name your attributes maybe_readonly and friends
> > to clearly indicate that this is only a guess by the user so at most
> > usable for diagnostics but never for optimization.
> >
> > Since we have quite costly attribute lookup I also prefer something
> > that translates to less attributes - how about
> > __attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
> > to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
> > then merge this into a single diag_arspec attribute instance we can
> > lookup.
>
> I can look into making a change along these lines.
>
> I'm not fond of the idea of introducing a "maybe" kind of attributes
> now and another parallel "for-sure" set later.  My goal is to have
> the attributes express the same access constraints as those on
> the arguments to built-in string functions like memcpy or strcat
> (i.e., read_only only reads a pointed-to object, write_only only
> writes, and, for strcat, read_write both reads and writes it).
>
> Those properties are sufficiently well understood.  The three
> attributes aren't intended to express constraints on aliasing
> or on the side-effects of the functions, like restrict or
> the const and pure attributes.

They at least sound like they do.  I didn't look at the latest version
of the patch but please amend the documentation of the attributes
to then say that they will never be used in a way affecting code
generation.

Thanks,
Richard.

>
> To let users do more than that, some additional annotation will
> probably be necessary.  In my WIP patch I have a no_side_effect
> attribute that lets functions do more than const and pure but
> that's still work in progress that I don't plan to submit for
> GCC 10.
>
> Martin
>
> >
> >>>
> >>> I don't see anything terribly concerning.  Looking forward to the final
> >>> iteration here.
> >>
> >> Attached is a subset of the original patch that just adds the three
> >> attributes and uses them to do buffer overflow checking.  I have
> >> also enhanced the detection of invalid arguments (null pointers,
> >> negative sizes).
> >>
> >> Retested on x86_64-linux.
> >>
> >> Martin
>
diff mbox series

Patch

PR c/80806 - gcc does not warn if local array is memset only
PR middle-end/83859 - attribute to associate buffer and its size

gcc/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
	* builtins.c (check_access): Make extern.  Consistently set
	the no-warning bit after issuing a warning.
	* builtins.h (check_access): Declare.
	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
	read_only and write_only attributes.
	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
	(__strcpy_chk, __strncpy_chk): Same.
	* calls.c (rdwr_access_hash): New type.
	(rdwr_map): Same.
	(init_attr_rdwr_indices): New function.
	(maybe_warn_rdwr_sizes): Same.
	(initialize_argument_information): Call init_attr_rdwr_indices.
	Call maybe_warn_rdwr_sizes.
	* doc/extend.texi (no_side_effect): Document new attribute.
	(read_only, write_only, read_write): Same.
	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
	(warn_uninitialized_vars): Rename argument.  Factor out code into
	maybe_warn_uninit_accesss.  Call it.

gcc/c/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
	(c_parser_expr_list): Add argument.
	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
	(c_parser_initializer): Set parser->in_init.
	(c_parser_binary_expression): Use parser->no_set_read.
	(c_parser_unary_expression): Same.
	(c_parser_sizeof_expression): Use parser->in_arg.
	(c_parser_postfix_expression_after_primary): Adjust.
	(is_write_only_p): New function.
	(c_parser_expr_list): Add argument.
	Avoid setting DECL_READ_P for decls passed to write-only function
	arguments.
	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
	(c_parser_oacc_wait_list): Same.
	* c-tree.h (parser_build_binary_op): Add argument.
	* c-typeck.c (default_conversion): Same.  Use it.
	(parser_build_binary_op): Same.
	(build_binary_op): Same.

gcc/c-family/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-attribs.c (handle_no_side_effect_attribute): New function.
	(handle_read_only_attribute, handle_write_only_attribute): Same.
	(handle_read_write_attribute): Same.
	(c_common_attribute_table): Add new attributes.
	(get_argument_type): New function.
	(handle_rdwr_attributes): Same.
	(has_attribute): Add argument to callback signature.  Pass to it
	a new value.
	(get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	* c-common.c (get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	(build_binary_op): Add a new argument.
	(default_conversion): Same.
	(has_attribute): Adjust argument type.

gcc/cp/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* typeck.c (cp_default_conversion): Add argument.
	(build_binary_op): Same.

gcc/testsuite/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
	* gcc.dg/attr-alloc_size.c: Adjust.
	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
	* gcc.dg/nonnull-3.c: Adjust.
	* gcc.dg/pr40340-2.c: Adjust.
	* gcc.dg/pr78768.c: Adjust.
	* gcc.dg/pr79715.c: Adjust.
	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index b89be5834de..6a1cb3270d2 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -33,6 +33,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "pretty-print.h"
 #include "intl.h"
+#include "gimple.h"
 
 /* Table of the tables of attributes (common, language, format, machine)
    searched.  */
@@ -2021,6 +2022,135 @@  maybe_diag_alias_attributes (tree alias, tree target)
     }
 }
 
+/* Given function type FNTYPE, initialize ACCESS based on the function's
+   first access attribute specifier, if one exists, or advance it to the
+   next specifier if it's already been initialized.  Return true if
+   ACCESS has been initialized or advanced to a valid access specifier,
+   false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (tree fntype, attr_access *access)
+{
+  if (!access->attrs)
+    access->attrs = TYPE_ATTRIBUTES (fntype);
+  else if (TREE_CODE (access->attrs) != TREE_LIST)
+    {
+      /* The last call sets ACCESS->ATTRS to something other than
+	 TREE_LIST to indicate it's reached the end.  */
+      return false;
+    }
+
+  if (!access->attrs)
+    return false;
+
+  const char *attr_name;
+  switch (access->kind)
+    {
+    case attr_access::read_only:
+      attr_name = "read_only";
+      break;
+
+    case attr_access::read_write:
+      attr_name = "read_write";
+      break;
+
+    case attr_access::write_only:
+      attr_name = "write_only";
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  access->attrs = lookup_attribute (attr_name, access->attrs);
+  if (!access->attrs)
+    return false;
+
+  /* When operands are specified, iterate over them.  */
+  tree opers = TREE_VALUE (access->attrs);
+  if (opers)
+    access->ptrarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    {
+      /* When no operands are specified, the attribute applies to every
+	 pointer argument.  Use ARGCHAIN to find the next such argument
+	 if it exists.  */
+      if (access->argchain)
+	access->argchain = TREE_CHAIN (access->argchain);
+      else
+	{
+	  access->ptrarg = 0;
+	  access->argchain = TYPE_ARG_TYPES (fntype);
+	}
+
+      while (!POINTER_TYPE_P (TREE_VALUE (access->argchain)))
+	{
+	  access->argchain = TREE_CHAIN (access->argchain);
+	  if (!access->argchain)
+	    return false;
+	  ++access->ptrarg;
+	}
+    }
+
+  if (opers)
+    opers = TREE_CHAIN (opers);
+
+  if (opers)
+    access->sizarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    access->sizarg = UINT_MAX;
+
+  access->ptr = NULL_TREE;
+  access->size = NULL_TREE;
+  access->attrs = TREE_CHAIN (access->attrs);
+
+  /* When the last attribute/argument has been reached set ACCESS->ATTRS
+     to something other than TREE_LIST to indicate that to the next call.  */
+  if (!access->attrs && !access->argchain)
+    access->attrs = void_type_node;
+
+  return true;
+}
+
+/* Given a Gimple statement STMT that's a function call, initialize ACCESS
+   based on the called function's first access attribute specifier, if one
+   exists, or advance it to the next specifier if it's already been
+   initialized.  Return true if ACCESS has been initialized or advanced
+   to a valid access specifier, false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (const gimple *stmt, attr_access *access)
+{
+  const gcall *gc = as_a <const gcall *> (stmt);
+  if (!gc)
+    return false;
+
+  tree fntype = gimple_call_fntype (gc);
+  if (!fntype)
+    return false;
+
+  if (!get_attr_access_ptr_and_size (fntype, access))
+    return false;
+
+  if (access->ptrarg < gimple_call_num_args (gc))
+    {
+      tree ptr = gimple_call_arg (gc, access->ptrarg);
+      access->ptr = POINTER_TYPE_P (TREE_TYPE (ptr)) ? ptr : NULL_TREE;
+    }
+  else
+    access->ptr = NULL_TREE;
+
+  if (access->sizarg <= gimple_call_num_args (gc))
+    {
+      tree size = gimple_call_arg (gc, access->sizarg);
+      access->size = INTEGRAL_TYPE_P (TREE_TYPE (size)) ? size : NULL_TREE;
+    }
+  else
+    access->size = NULL_TREE;
+
+  return access->ptr;
+}
+
 
 #if CHECKING_P
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 23a7321e04a..0e2320701b1 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -218,4 +218,36 @@  lookup_attribute_by_prefix (const char *attr_name, tree list)
     }
 }
 
+/* Description of a function argument declared attribute read_only,
+   read_write, or write_only.  Used as an "iterator" over all such
+   arguments in a function declaration or call.  */
+
+struct attr_access
+{
+  /* Attribute chain for the given function declaration.  */
+  tree attrs;
+
+  /* The attribute pointer argument.  */
+  tree ptr;
+  /* The size of the pointed-to object or NULL when not specified.  */
+  tree size;
+
+  /* The tree node corresponding to the current argument in the chain
+     of formal function arguments in a call to the given function.
+     Used by attributes that specify that relevant arguments are of
+     the given kind, as in
+       strcmp (const char*, const char*) __attribute__ ((read_only))  */
+  tree argchain;
+
+  /* The zero-based number of each of the formal function arguments.  */
+  unsigned ptrarg;
+  unsigned sizarg;
+  enum Kind { read_only, write_only, read_write };
+
+  Kind kind;
+};
+
+extern bool get_attr_access_ptr_and_size (tree, attr_access *);
+extern bool get_attr_access_ptr_and_size (const gimple *, attr_access *);
+
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index 39d1395f42a..1d110dd3398 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -119,6 +119,10 @@  DEF_ATTR_IDENT (ATTR_TM_TMPURE, "transaction_pure")
 DEF_ATTR_IDENT (ATTR_RETURNS_TWICE, "returns_twice")
 DEF_ATTR_IDENT (ATTR_RETURNS_NONNULL, "returns_nonnull")
 DEF_ATTR_IDENT (ATTR_WARN_UNUSED_RESULT, "warn_unused_result")
+DEF_ATTR_IDENT (ATTR_NO_SIDE_EFFECT, "no_side_effect")
+DEF_ATTR_IDENT (ATTR_READ_ONLY, "read_only")
+DEF_ATTR_IDENT (ATTR_READ_WRITE, "read_write")
+DEF_ATTR_IDENT (ATTR_WRITE_ONLY, "write_only")
 
 DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL)
 
@@ -212,6 +216,7 @@  DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL, ATTR_NONNULL, ATTR_NULL, \
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF, ATTR_NONNULL, ATTR_NULL, \
 			ATTR_NOTHROW_LEAF_LIST)
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF_LIST, ATTR_LEAF, ATTR_NULL, ATTR_NOTHROW_NONNULL_LEAF)
+
 /* Nothrow functions whose first parameter is a nonnull pointer.  */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1, ATTR_NONNULL, ATTR_LIST_1, \
 			ATTR_NOTHROW_LIST)
@@ -271,10 +276,54 @@  DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_TYPEGENERIC_LEAF,
 /* Nothrow const functions whose pointer parameter(s) are all nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_CONST_NOTHROW_NONNULL, ATTR_CONST, ATTR_NULL, \
 			ATTR_NOTHROW_NONNULL)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
-   and which return their first argument.  */
+   which return their first argument, and which access their arguments
+   for reading and writing as indicated by RDONLY and WRONLY.  */
 DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_LEAF, ATTR_FNSPEC, ATTR_LIST_STR1, \
 			ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_2_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_2, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY2_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_2_3, ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+/* strcat.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF,
+		    ATTR_READ_WRITE, ATTR_LIST_1,
+		    ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF)
+
+/* memcpy, memmove.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
+/* strcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF,
+ 		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_LEAF)
+
+/* strncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
    and return value is also nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_NOTHROW_LEAF, ATTR_RETURNS_NONNULL, ATTR_NULL, \
@@ -305,6 +354,52 @@  DEF_ATTR_TREE_LIST (ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_WARN_UNUS
 DEF_ATTR_TREE_LIST (ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \
 			ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
 
+/* strdup, strndup.  */
+DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
+
+/* strchr, strlen. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_PURE_NOTHROW_NONNULL_LEAF)
+
+/* strcmp, strstr. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+
+/* mempcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1_3,
+		    ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF)
+
+/* memchr.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1_3,
+		    ATTR_NOTHROW_NONNULL_LEAF)
+
+/* memcmp.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3, \
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF)
+
+/* stpcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
+/* stpncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
 /* Construct a tree for the format attribute (and implicitly nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE(TYPE, FA, VALUES)				 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 1fd4b88bcac..d7f88f224e9 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3299,7 +3299,7 @@  determine_block_size (tree len, rtx len_rtx,
    If the call is successfully verified as safe return true, otherwise
    return false.  */
 
-static bool
+bool
 check_access (tree exp, tree, tree, tree dstwrite,
 	      tree maxread, tree srcstr, tree dstsize)
 {
@@ -3433,37 +3433,42 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	  location_t loc = tree_nonartificial_location (exp);
 	  loc = expansion_point_location_if_in_system_header (loc);
 
+	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
 	      /* This is a call to strcpy with a destination of 0 size
 		 and a source of unknown length.  The call will write
 		 at least one byte past the end of the destination.  */
-	      warning_at (loc, opt,
-			  "%K%qD writing %E or more bytes into a region "
-			  "of size %E overflows the destination",
-			  exp, func, range[0], dstsize);
+	      warned = warning_at (loc, opt,
+				   "%K%qD writing %E or more bytes into "
+				   "a region of size %E overflows "
+				   "the destination",
+				   exp, func, range[0], dstsize);
 	    }
 	  else if (tree_int_cst_equal (range[0], range[1]))
-	    warning_n (loc, opt, tree_to_uhwi (range[0]),
-		       "%K%qD writing %E byte into a region "
-		       "of size %E overflows the destination",
-		       "%K%qD writing %E bytes into a region "
-		       "of size %E overflows the destination",
-		       exp, func, range[0], dstsize);
+	    warned = warning_n (loc, opt, tree_to_uhwi (range[0]),
+				"%K%qD writing %E byte into a region "
+				"of size %E overflows the destination",
+				"%K%qD writing %E bytes into a region "
+				"of size %E overflows the destination",
+				exp, func, range[0], dstsize);
 	  else if (tree_int_cst_sign_bit (range[1]))
 	    {
 	      /* Avoid printing the upper bound if it's invalid.  */
-	      warning_at (loc, opt,
-			  "%K%qD writing %E or more bytes into a region "
-			  "of size %E overflows the destination",
-			  exp, func, range[0], dstsize);
+	      warned = warning_at (loc, opt,
+				   "%K%qD writing %E or more bytes into "
+				   "a region of size %E overflows "
+				   "the destination",
+				   exp, func, range[0], dstsize);
 	    }
 	  else
-	    warning_at (loc, opt,
-			"%K%qD writing between %E and %E bytes into "
-			"a region of size %E overflows the destination",
-			exp, func, range[0], range[1],
-			dstsize);
+	    warned = warning_at (loc, opt,
+				 "%K%qD writing between %E and %E bytes into "
+				 "a region of size %E overflows the destination",
+				 exp, func, range[0], range[1],
+				 dstsize);
+	  if (warned)
+	    TREE_NO_WARNING (exp) = true;
 
 	  /* Return error when an overflow has been detected.  */
 	  return false;
@@ -3486,21 +3491,26 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	      if (TREE_NO_WARNING (exp))
 		return false;
 
+	      bool warned = false;
+
 	      /* Warn about crazy big sizes first since that's more
 		 likely to be meaningful than saying that the bound
 		 is greater than the object size if both are big.  */
 	      if (range[0] == range[1])
-		warning_at (loc, opt,
-			    "%K%qD specified bound %E "
-			    "exceeds maximum object size %E",
-			    exp, func,
-			    range[0], maxobjsize);
+		warned = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     range[0], maxobjsize);
 	      else
-		warning_at (loc, opt,
-			    "%K%qD specified bound between %E and %E "
-			    "exceeds maximum object size %E",
-			    exp, func,
-			    range[0], range[1], maxobjsize);
+		warned = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     range[0], range[1], maxobjsize);
+
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
 
 	      return false;
 	    }
@@ -3510,18 +3520,23 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	      if (TREE_NO_WARNING (exp))
 		return false;
 
+	      bool warned = false;
+
 	      if (tree_int_cst_equal (range[0], range[1]))
-		warning_at (loc, opt,
-			    "%K%qD specified bound %E "
-			    "exceeds destination size %E",
-			    exp, func,
-			    range[0], dstsize);
+		warned = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     range[0], dstsize);
 	      else
-		warning_at (loc, opt,
-			    "%K%qD specified bound between %E and %E "
-			    "exceeds destination size %E",
-			    exp, func,
-			    range[0], range[1], dstsize);
+		warned = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     range[0], range[1], dstsize);
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
+
 	      return false;
 	    }
 	}
@@ -3536,26 +3551,31 @@  check_access (tree exp, tree, tree, tree dstwrite,
       if (TREE_NO_WARNING (exp))
 	return false;
 
+      bool warned = false;
       location_t loc = tree_nonartificial_location (exp);
+      loc = expansion_point_location_if_in_system_header (loc);
 
       if (tree_int_cst_equal (range[0], range[1]))
-	warning_n (loc, opt, tree_to_uhwi (range[0]),
-		   "%K%qD reading %E byte from a region of size %E",
-		   "%K%qD reading %E bytes from a region of size %E",
-		    exp, func, range[0], slen);
+	warned = warning_n (loc, opt, tree_to_uhwi (range[0]),
+			    "%K%qD reading %E byte from a region of size %E",
+			    "%K%qD reading %E bytes from a region of size %E",
+			    exp, func, range[0], slen);
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
-	  warning_at (loc, opt,
-		      "%K%qD reading %E or more bytes from a region "
-		      "of size %E",
-		      exp, func, range[0], slen);
+	  warned = warning_at (loc, opt,
+			       "%K%qD reading %E or more bytes from a region "
+			       "of size %E",
+			       exp, func, range[0], slen);
 	}
       else
-	warning_at (loc, opt,
-		    "%K%qD reading between %E and %E bytes from a region "
-		    "of size %E",
-		    exp, func, range[0], range[1], slen);
+	warned = warning_at (loc, opt,
+			     "%K%qD reading between %E and %E bytes from "
+			     "a region of size %E",
+			     exp, func, range[0], range[1], slen);
+      if (warned)
+	TREE_NO_WARNING (exp) = true;
+
       return false;
     }
 
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 8bb7027aac7..217e6aa01c8 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -690,36 +690,36 @@  DEF_C99_COMPL_BUILTIN        (BUILT_IN_CTANL, "ctanl", BT_FN_COMPLEX_LONGDOUBLE_
 /* bcmp, bcopy and bzero have traditionally accepted NULL pointers
    when the length parameter is zero, so don't apply attribute "nonnull".  */
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCMP, "bcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCOPY, "bcopy", BT_FN_VOID_CONST_PTR_PTR_SIZE, ATTR_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_BZERO, "bzero", BT_FN_VOID_PTR_SIZE, ATTR_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_INDEX, "index", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_MEMCHR, "memchr", BT_FN_PTR_CONST_PTR_INT_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_MEMCMP, "memcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMCPY, "memcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMMOVE, "memmove", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY, "mempcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMSET, "memset", BT_FN_PTR_PTR_INT_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_RINDEX, "rindex", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPCPY, "stpcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY, "stpncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCASECMP, "strcasecmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCAT, "strcat", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCHR, "strchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCMP, "strcmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCPY, "strcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCSPN, "strcspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRDUP, "strdup", BT_FN_STRING_CONST_STRING, ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNDUP, "strndup", BT_FN_STRING_CONST_STRING_SIZE, ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRLEN, "strlen", BT_FN_SIZE_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCASECMP, "strncasecmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCAT, "strncat", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCOPY, "bcopy", BT_FN_VOID_CONST_PTR_PTR_SIZE, ATTR_NOTHROW_WRONLY2_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_BZERO, "bzero", BT_FN_VOID_PTR_SIZE, ATTR_NOTHROW_WRONLY1_2_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_INDEX, "index", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMCHR, "memchr", BT_FN_PTR_CONST_PTR_INT_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMCMP, "memcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_MEMCPY, "memcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMMOVE, "memmove", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY, "mempcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMSET, "memset", BT_FN_PTR_PTR_INT_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_RINDEX, "rindex", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN   (BUILT_IN_STPCPY, "stpcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY, "stpncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCASECMP, "strcasecmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCAT, "strcat", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCHR, "strchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRCMP, "strcmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCPY, "strcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRCSPN, "strcspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRDUP, "strdup", BT_FN_STRING_CONST_STRING, ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNDUP, "strndup", BT_FN_STRING_CONST_STRING_SIZE, ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRLEN, "strlen", BT_FN_SIZE_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCASECMP, "strncasecmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCAT, "strncat", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNLEN, "strnlen", BT_FN_SIZE_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
 DEF_LIB_BUILTIN        (BUILT_IN_STRPBRK, "strpbrk", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
 
 /* Category: stdio builtins.  */
 DEF_LIB_BUILTIN        (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3)
@@ -865,7 +865,7 @@  DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
 DEF_EXT_LIB_BUILTIN        (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
 /* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed.  */
-DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
+DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_WRONLY1_LEAF)
 DEF_GCC_BUILTIN        (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_GETTEXT, "gettext", BT_FN_STRING_CONST_STRING, ATTR_FORMAT_ARG_1)
 DEF_C99_BUILTIN        (BUILT_IN_IMAXABS, "imaxabs", BT_FN_INTMAX_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
@@ -962,16 +962,16 @@  DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq")
 
 /* Object size checking builtins.  */
 DEF_GCC_BUILTIN	       (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY_CHK, "__mempcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN   (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPCPY_CHK, "__stpcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY_CHK, "__stpncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCAT_CHK, "__strcat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCAT_CHK, "__strncat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SNPRINTF_CHK, "__snprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_5_6)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_5)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSNPRINTF_CHK, "__vsnprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_5_0)
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 1ad82e86963..2deb603059b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -151,5 +151,7 @@  extern internal_fn replacement_internal_fn (gcall *);
 extern void warn_string_no_nul (location_t, const char *, tree, tree);
 extern tree unterminated_array (tree, tree * = NULL, bool * = NULL);
 extern bool builtin_with_linkage_p (tree);
+extern bool check_access (tree, tree, tree, tree, tree, tree, tree);
+
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 6500b998321..e699f429618 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -125,6 +125,10 @@  static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
 static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *);
 static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
 						 bool *);
+static tree handle_read_only_attribute (tree *, tree, tree, int, bool *);
+static tree handle_read_write_attribute (tree *, tree, tree, int, bool *);
+static tree handle_write_only_attribute (tree *, tree, tree, int, bool *);
+
 static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
 static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *);
 static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *);
@@ -468,6 +472,12 @@  const struct attribute_spec c_common_attribute_table[] =
 			      handle_copy_attribute, NULL },
   { "noinit",		      0, 0, true,  false, false, false,
 			      handle_noinit_attribute, attr_noinit_exclusions },
+  { "read_only",              0, 2, false, true, true, false,
+			      handle_read_only_attribute, NULL },
+  { "write_only",             0, 2, false, true, true, false,
+			      handle_write_only_attribute, NULL },
+  { "read_write",             0, 2, false, true, true, false,
+			      handle_read_write_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -3775,6 +3785,228 @@  handle_nonstring_attribute (tree *node, tree name, tree ARG_UNUSED (args),
   return NULL_TREE;
 }
 
+/* Given a function type FUNCTYPE, returns the type of the parameter
+   ARGNO or null if ARGNO exceeds the number of parameters.  On failure
+   set *NARGS to the number of function parameters.  */
+
+static tree
+get_argument_type (tree functype, unsigned argno, unsigned *nargs)
+{
+  function_args_iterator iter;
+  function_args_iter_init (&iter, functype);
+
+  unsigned count = 0;
+
+  for ( ; ; ++count, function_args_iter_next (&iter))
+    {
+      if (count + 1 == argno)
+	{
+	  tree argtype = function_args_iter_cond (&iter);
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  return argtype;
+	}
+    }
+
+  *nargs = count;
+  return NULL_TREE;
+}
+
+/* Handle attributes read_only, write_only, and read_write.  */
+
+static tree
+handle_rdwr_attributes (bool writeable, tree *node, tree name,
+			tree operands, int ARG_UNUSED (flags),
+			bool *no_add_attrs)
+{
+  tree type = *node;
+  tree attrs = TYPE_ATTRIBUTES (type);
+
+  /* If no operands are specified, all non-const pointer arguments are
+     treated as write-only.  Verify a full prototype is given so that
+     the arguments will have the correct types when we actually check
+     them later.  Avoid diagnosing type-generic built-ins since those
+     have no prototype.  */
+  if (!operands
+      && !prototype_p (type)
+      && (!attrs || !lookup_attribute ("type generic", attrs)))
+    {
+      error ("attribute %qE without arguments on a non-prototype", name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* Attribute operands have been specified.  Verify that each operand
+     value references a non-const pointer argument to the function.  */
+  tree idxnodes[2] = { NULL_TREE, NULL_TREE };
+  tree argtypes[2] = { NULL_TREE, NULL_TREE };
+  unsigned HOST_WIDE_INT idxs[2] = { 0, 0 };
+
+  /* Number of function formal arguments (used in diagnostics).  */
+  unsigned argcount = 0;
+  /* Number of attribute operands.  */
+  unsigned opcount = 0;
+
+  for (unsigned i = 0; i != 2; ++i, operands = TREE_CHAIN (operands), ++opcount)
+    {
+      if (!operands)
+	break;
+
+      idxnodes[i] = TREE_VALUE (operands);
+
+      if (TREE_CODE (idxnodes[i]) != IDENTIFIER_NODE
+	  && TREE_CODE (idxnodes[i]) != FUNCTION_DECL)
+	idxnodes[i] = default_conversion (idxnodes[i]);
+
+      if (!get_attribute_operand (idxnodes[i], idxs + i))
+	{
+	  *no_add_attrs = true;
+	  idxs[i] = i + 1;
+	}
+
+      argtypes[i] = get_argument_type (type, idxs[i], &argcount);
+    }
+
+  if (*no_add_attrs)
+    {
+      /* Dignose an invalid operand and bail.  */
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> invalid operand %wu",
+	       name, idxnodes[0], idxnodes[1], idxs[0] ? idxs[0] : idxs[1]);
+      else
+	error ("attribute %<%E(%E)%> invalid operand",
+	       name, idxnodes[0]);
+
+      return NULL_TREE;
+    }
+
+  if (!argtypes[0] || (idxnodes[1] && !argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> operand exceeds number "
+	       "of arguments %u",
+	       name, idxnodes[0], idxnodes[1], argcount);
+      else if (idxnodes[0])
+	error ("attribute %<%E(%E)%> operand exceeds number of arguments %u",
+	       name, idxnodes[0], argcount);
+      else if (!opcount)
+	{
+	  /* FIXME: reject empty attribute write_only on function
+	     declarations with no non-const object pointer arguments.  */
+	  tree arg;
+	  function_args_iterator it;
+	  FOREACH_FUNCTION_ARGS (*node, arg, it)
+	    if (POINTER_TYPE_P (arg))
+	      return NULL_TREE;
+
+	  error ("attribute %<%E%> on a function with no pointer arguments",
+		 name);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+      else
+	error ("attribute %<%E%> invalid operand",
+	       name);
+
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (argtypes[0]) != POINTER_TYPE)
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> first operand references "
+	       "non-pointer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[0]);
+      else
+	error ("attribute %<%E(%E)%> references non-pointer argument "
+	       "type %qT",
+	       name, idxnodes[0], argtypes[0]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (writeable)
+    {
+      /* A read_write and write_only attributes must reference non-const
+	 arguments.  */
+      if (TYPE_READONLY (TREE_TYPE (argtypes[0])))
+	{
+	  if (idxnodes[1])
+	    error ("attribute %<%E(%E, %E)%> first operand references "
+		   "%qs-qualified argument type %qT",
+		   name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+	  else
+	    error ("attribute %<%E(%E)%> operand references %qs-qualified "
+		   "argument type %qT",
+		   name, idxnodes[0], "const", argtypes[0]);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+    }
+  else if (!TYPE_READONLY (TREE_TYPE (argtypes[0])))
+    {
+      /* A read_only attributes should reference const-qualified arguments
+	 but it's not an error if one doesn't.  */
+      if (idxnodes[1])
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E, %E)%> first operand references "
+		 "non-%qs argument type %qT",
+		 name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+      else
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E)%> operand references non-%qs "
+		 "argument type %qT",
+		 name, idxnodes[0], "const", argtypes[0]);
+    }
+
+  if (argtypes[1] && !INTEGRAL_TYPE_P (argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[1]);
+      else
+	error ("attribute %<%E(%E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], argtypes[1]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle attribute read_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_only_attribute (tree *node, tree name, tree operands, int flags,
+			    bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (false, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute write_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_write_only_attribute (tree *node, tree name, tree operands, int flags,
+			     bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute read_write ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_write_attribute (tree *node, tree name,
+			     tree operands, int flags, bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -4215,7 +4447,7 @@  validate_attribute (location_t atloc, tree oper, tree attr)
    consider just its type.  */
 
 bool
-has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
+has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree, bool))
 {
   if (!attr || !t || t == error_mark_node)
     return false;
@@ -4348,8 +4580,8 @@  has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 		    return false;
 
 		  /* Convert to make them equality-comparable.  */
-		  v1 = convert (v1);
-		  v2 = convert (v2);
+		  v1 = convert (v1, false);
+		  v2 = convert (v2, false);
 
 		  /* A positive value indicates equality, negative means
 		     "don't know."  */
@@ -4369,7 +4601,7 @@  has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	    {
 	      if (tree arg = TREE_VALUE (attr))
 		{
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  if (!tree_fits_uhwi_p (arg))
 		    /* Invalid argument.  */;
 		  else if (expr && DECL_P (expr)
@@ -4416,7 +4648,7 @@  has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	      if (tree arg = TREE_VALUE (attr))
 		{
 		  /* Compare the vector size argument for equality.  */
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  return tree_int_cst_equal (arg, TYPE_SIZE_UNIT (type)) == 1;
 		}
 	      else
@@ -4426,7 +4658,7 @@  has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	    {
 	      if (tree arg = TREE_VALUE (attr))
 		{
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  if (expr && DECL_P (expr))
 		    found_match = (DECL_WARN_IF_NOT_ALIGN (expr)
 				   == tree_to_uhwi (arg) * BITS_PER_UNIT);
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 3756219e5cf..259d7f44008 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5479,7 +5479,7 @@  nonnull_check_p (tree args, unsigned HOST_WIDE_INT param_num)
 
   for (; args; args = TREE_CHAIN (args))
     {
-      bool found = get_nonnull_operand (TREE_VALUE (args), &arg_num);
+      bool found = get_attribute_operand (TREE_VALUE (args), &arg_num);
 
       gcc_assert (found);
 
@@ -5514,11 +5514,11 @@  check_nonnull_arg (void *ctx, tree param, unsigned HOST_WIDE_INT param_num)
     }
 }
 
-/* Helper for nonnull attribute handling; fetch the operand number
-   from the attribute argument list.  */
+/* Helper for attribute handling; fetch the operand number from
+   the attribute argument list.  */
 
 bool
-get_nonnull_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp)
+get_attribute_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp)
 {
   /* Verify the arg number is a small constant.  */
   if (tree_fits_uhwi_p (arg_num_expr))
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index a9f4d0c5c11..eb8ac57aba4 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -869,7 +869,7 @@  extern bool pointer_to_zero_sized_aggr_p (tree);
 extern bool bool_promoted_to_int_p (tree);
 extern tree fold_for_warn (tree);
 extern tree c_common_get_narrower (tree, int *);
-extern bool get_nonnull_operand (tree, unsigned HOST_WIDE_INT *);
+extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
 
 #define c_sizeof(LOC, T)  c_sizeof_or_alignof_type (LOC, T, true, false, 1)
 #define c_alignof(LOC, T) c_sizeof_or_alignof_type (LOC, T, false, false, 1)
@@ -969,13 +969,14 @@  extern tree build_real_imag_expr (location_t, enum tree_code, tree);
    a variant of the C language.  They are used in c-common.c.  */
 
 extern tree build_unary_op (location_t, enum tree_code, tree, bool);
-extern tree build_binary_op (location_t, enum tree_code, tree, tree, bool);
+extern tree build_binary_op (location_t, enum tree_code, tree, tree, bool,
+			     bool = true);
 extern tree perform_integral_promotions (tree);
 
 /* These functions must be defined by each front-end which implements
    a variant of the C language.  They are used by port files.  */
 
-extern tree default_conversion (tree);
+extern tree default_conversion (tree, bool = true);
 
 /* Given two integer or real types, return the type for their sum.
    Given two compatible ANSI C types, returns the merged type.  */
@@ -1376,7 +1377,7 @@  extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree, bool));
 
 #if CHECKING_P
 namespace selftest {
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 6070502deb5..1f97a259951 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -187,6 +187,14 @@  struct GTY(()) c_parser {
   BOOL_BITFIELD in_if_block : 1;
   /* True if we want to lex an untranslated string.  */
   BOOL_BITFIELD lex_untranslated_string : 1;
+  /* True if taking the address of a DECL should not set its DECL_READ_P
+     flag.  */
+  BOOL_BITFIELD no_set_read : 1;
+  /* True when parsing an initializer expression.  */
+  BOOL_BITFIELD in_init : 1;
+  /* True when parsing a non-empty expression list such as a list of
+     arguments in a function call.  */
+  BOOL_BITFIELD in_expr_list : 1;
 
   /* Objective-C specific parser/lexer information.  */
 
@@ -1447,7 +1455,8 @@  static tree c_parser_transaction_cancel (c_parser *);
 static struct c_expr c_parser_expression (c_parser *);
 static struct c_expr c_parser_expression_conv (c_parser *);
 static vec<tree, va_gc> *c_parser_expr_list (c_parser *, bool, bool,
-					     vec<tree, va_gc> **, location_t *,
+					     vec<tree, va_gc> **, tree,
+					     location_t *,
 					     tree *, vec<location_t> *,
 					     unsigned int * = NULL);
 static struct c_expr c_parser_has_attribute_expression (c_parser *);
@@ -4431,7 +4440,7 @@  c_parser_attribute (c_parser *parser, tree attrs,
 	  tree tree_list;
 	  c_parser_consume_token (parser);
 	  expr_list = c_parser_expr_list (parser, false, true,
-					  NULL, NULL, NULL, NULL);
+					  NULL, NULL, NULL, NULL, NULL);
 	  tree_list = build_tree_list_vec (expr_list);
 	  attr_args = tree_cons (NULL_TREE, arg1, tree_list);
 	  release_tree_vector (expr_list);
@@ -4444,7 +4453,7 @@  c_parser_attribute (c_parser *parser, tree attrs,
       else
 	{
 	  expr_list = c_parser_expr_list (parser, false, true,
-					  NULL, NULL, NULL, NULL);
+					  NULL, NULL, NULL, NULL, NULL);
 	  attr_args = build_tree_list_vec (expr_list);
 	  release_tree_vector (expr_list);
 	}
@@ -4624,12 +4633,17 @@  c_parser_initializer (c_parser *parser)
     return c_parser_braced_init (parser, NULL_TREE, false, NULL);
   else
     {
+      const bool saved_in_init = parser->in_init;
+      parser->in_init = true;
+
       struct c_expr ret;
       location_t loc = c_parser_peek_token (parser)->location;
       ret = c_parser_expr_no_commas (parser, NULL);
       if (TREE_CODE (ret.value) != STRING_CST
 	  && TREE_CODE (ret.value) != COMPOUND_LITERAL_EXPR)
 	ret = convert_lvalue_to_rvalue (loc, ret, true, true);
+
+      parser->in_init = saved_in_init;
       return ret;
     }
 }
@@ -7002,6 +7016,9 @@  c_parser_binary_expression (c_parser *parser, struct c_expr *after,
     tree sizeof_arg;
   } stack[NUM_PRECS];
   int sp;
+
+  bool read_p = !parser->no_set_read;
+
   /* Location of the binary operator.  */
   location_t binary_loc = UNKNOWN_LOCATION;  /* Quiet warning.  */
 #define POP								      \
@@ -7050,10 +7067,10 @@  c_parser_binary_expression (c_parser *parser, struct c_expr *after,
       }									      \
     stack[sp - 1].expr							      \
       = convert_lvalue_to_rvalue (stack[sp - 1].loc,			      \
-				  stack[sp - 1].expr, true, true);	      \
+				  stack[sp - 1].expr, true, read_p);	      \
     stack[sp].expr							      \
       = convert_lvalue_to_rvalue (stack[sp].loc,			      \
-				  stack[sp].expr, true, true);		      \
+				  stack[sp].expr, true, read_p);	      \
     if (__builtin_expect (omp_atomic_lhs != NULL_TREE, 0) && sp == 1	      \
 	&& c_parser_peek_token (parser)->type == CPP_SEMICOLON		      \
 	&& ((1 << stack[sp].prec)					      \
@@ -7071,7 +7088,8 @@  c_parser_binary_expression (c_parser *parser, struct c_expr *after,
       stack[sp - 1].expr = parser_build_binary_op (stack[sp].loc,	      \
 						   stack[sp].op,	      \
 						   stack[sp - 1].expr,	      \
-						   stack[sp].expr);	      \
+						   stack[sp].expr,	      \
+						   read_p);		      \
     sp--;								      \
   } while (0)
   gcc_assert (!after || c_dialect_objc ());
@@ -7176,7 +7194,7 @@  c_parser_binary_expression (c_parser *parser, struct c_expr *after,
 	  src_range = stack[sp].expr.src_range;
 	  stack[sp].expr
 	    = convert_lvalue_to_rvalue (stack[sp].loc,
-					stack[sp].expr, true, true);
+					stack[sp].expr, true, read_p);
 	  stack[sp].expr.value = c_objc_common_truthvalue_conversion
 	    (stack[sp].loc, default_conversion (stack[sp].expr.value));
 	  c_inhibit_evaluation_warnings += (stack[sp].expr.value
@@ -7187,7 +7205,7 @@  c_parser_binary_expression (c_parser *parser, struct c_expr *after,
 	  src_range = stack[sp].expr.src_range;
 	  stack[sp].expr
 	    = convert_lvalue_to_rvalue (stack[sp].loc,
-					stack[sp].expr, true, true);
+					stack[sp].expr, true, read_p);
 	  stack[sp].expr.value = c_objc_common_truthvalue_conversion
 	    (stack[sp].loc, default_conversion (stack[sp].expr.value));
 	  c_inhibit_evaluation_warnings += (stack[sp].expr.value
@@ -7339,7 +7357,10 @@  c_parser_unary_expression (c_parser *parser)
     case CPP_AND:
       c_parser_consume_token (parser);
       op = c_parser_cast_expression (parser, NULL);
-      mark_exp_read (op.value);
+      /* Avoid setting DECL_READ_P of a referenced object when its
+	 address is being passed to a write-only function parameter.  */
+      if (!parser->no_set_read)
+	mark_exp_read (op.value);
       return parser_build_unary_op (op_loc, ADDR_EXPR, op);
     case CPP_MULT:
       {
@@ -7502,7 +7523,10 @@  c_parser_sizeof_expression (c_parser *parser)
     sizeof_expr:
       c_inhibit_evaluation_warnings--;
       in_sizeof--;
-      mark_exp_read (expr.value);
+      /* Avoid setting DECL_READ_P of a sizeof operand when the result
+	 is passed to a function with a write-only function parameter.  */
+      if (!parser->in_expr_list)
+	mark_exp_read (expr.value);
       if (TREE_CODE (expr.value) == COMPONENT_REF
 	  && DECL_C_BIT_FIELD (TREE_OPERAND (expr.value, 1)))
 	error_at (expr_loc, "%<sizeof%> applied to a bit-field");
@@ -9618,8 +9642,10 @@  c_parser_postfix_expression_after_primary (c_parser *parser,
 	    exprlist = NULL;
 	  else
 	    exprlist = c_parser_expr_list (parser, true, false, &origtypes,
+					   expr.value,
 					   sizeof_arg_loc, sizeof_arg,
 					   &arg_loc, &literal_zero_mask);
+
 	  c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
 				     "expected %<)%>");
 	  orig_expr = expr;
@@ -9851,6 +9877,32 @@  c_parser_check_literal_zero (c_parser *parser, unsigned *literal_zero_mask,
     }
 }
 
+/* Return true if expression number IDX and type ARGTYPE eclared with
+   the write-only attribute in ATTRS is itself declared write-only.
+   ATTRS may be null.  */
+
+static bool
+is_write_only_p (tree attrs, tree argtype, unsigned idx)
+{
+  if (!attrs)
+    return false;
+
+  if (!POINTER_TYPE_P (argtype))
+    return false;
+
+  if (TYPE_READONLY (TREE_TYPE (argtype)))
+    return false;
+
+  for (tree a = attrs; a; a = TREE_CHAIN (a))
+    {
+      unsigned HOST_WIDE_INT wronlyidx;
+      if (!get_attribute_operand (a, &wronlyidx) || wronlyidx == idx)
+	return true;
+    }
+  return false;
+}
+
+
 /* Parse a non-empty list of expressions.  If CONVERT_P, convert
    functions and arrays to pointers and lvalues to rvalues.  If
    FOLD_P, fold the expressions.  If LOCATIONS is non-NULL, save the
@@ -9863,7 +9915,7 @@  c_parser_check_literal_zero (c_parser *parser, unsigned *literal_zero_mask,
 
 static vec<tree, va_gc> *
 c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
-		    vec<tree, va_gc> **p_orig_types,
+		    vec<tree, va_gc> **p_orig_types, tree func,
 		    location_t *sizeof_arg_loc, tree *sizeof_arg,
 		    vec<location_t> *locations,
 		    unsigned int *literal_zero_mask)
@@ -9881,9 +9933,44 @@  c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
 
   if (literal_zero_mask)
     c_parser_check_literal_zero (parser, literal_zero_mask, 0);
-  expr = c_parser_expr_no_commas (parser, NULL);
+
+  tree functype = func ? TREE_TYPE (func) : NULL_TREE;
+  if (functype)
+    {
+      while (POINTER_TYPE_P (functype))
+	functype = TREE_TYPE (functype);
+    }
+
+  tree attrs = (functype && functype != error_mark_node
+		? lookup_attribute ("write_only", TYPE_ATTRIBUTES (functype))
+		: NULL_TREE);
+
+  tree argtypes = attrs ? TYPE_ARG_TYPES (functype) : NULL_TREE;
+
+  /* Determine if the first function parameter in the list is declared
+     write-only and only mark the corresponding argument DECL_READ_P()
+     if it either isn't write-only or doesn't reference the address of
+     a declared object.  */
+  bool wronly = (argtypes
+		 && !parser->in_init
+		 && is_write_only_p (attrs, TREE_VALUE (argtypes), idx + 1));
+
+  {
+    /* Avoid setting the read bit for write-only decls.  */
+    bool save_set_read = parser->no_set_read;
+    parser->no_set_read = wronly;
+    expr = c_parser_expr_no_commas (parser, NULL);
+    parser->no_set_read = save_set_read;
+  }
+
   if (convert_p)
-    expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true, true);
+    {
+      /* Allow DECL_READ_P to be set if the expression is a pointer.  */
+      wronly &= !DECL_P (expr.value) || !POINTER_TYPE_P (TREE_TYPE (expr.value));
+      expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
+				       !wronly);
+    }
+
   if (fold_p)
     expr.value = c_fully_fold (expr.value, false, NULL);
   ret->quick_push (expr.value);
@@ -9902,10 +9989,41 @@  c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
       c_parser_consume_token (parser);
       if (literal_zero_mask)
 	c_parser_check_literal_zero (parser, literal_zero_mask, idx + 1);
-      expr = c_parser_expr_no_commas (parser, NULL);
+
+      /* Determine if the next function parameter in the list is declared
+	 write-only and only mark the corresponding argument DECL_READ_P()
+	 if it either isn't write-only or doesn't reference the address of
+	 a declared object.  */
+      if (argtypes && attrs)
+	{
+	  argtypes = TREE_CHAIN (argtypes);
+	  wronly = (!parser->in_init
+		    && is_write_only_p (attrs, TREE_VALUE (argtypes), idx + 2));
+	}
+      else
+	wronly = false;
+
+      {
+	/* Avoid setting the read bit for write-only decls.  */
+	bool save_set_read = parser->no_set_read;
+	parser->no_set_read = wronly;
+	/* Indicate a an expression list is being parsed.  */
+	bool save_in_expr_list = parser->in_expr_list;
+	parser->in_expr_list = true;
+	expr = c_parser_expr_no_commas (parser, NULL);
+	/* Restore both bits.  */
+	parser->no_set_read = save_set_read;
+	parser->in_expr_list = save_in_expr_list;
+      }
+
       if (convert_p)
-	expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
-					 true);
+	{
+	  wronly &= (!DECL_P (expr.value)
+		     || !POINTER_TYPE_P (TREE_TYPE (expr.value)));
+	  expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
+					   !wronly);
+	}
+
       if (fold_p)
 	expr.value = c_fully_fold (expr.value, false, NULL);
       vec_safe_push (ret, expr.value);
@@ -9923,6 +10041,7 @@  c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
     }
   if (orig_types)
     *p_orig_types = orig_types;
+
   return ret;
 }
 
@@ -11016,8 +11135,8 @@  static tree
 c_parser_objc_keywordexpr (c_parser *parser)
 {
   tree ret;
-  vec<tree, va_gc> *expr_list = c_parser_expr_list (parser, true, true,
-						NULL, NULL, NULL, NULL);
+  vec<tree, va_gc> *expr_list = c_parser_expr_list (parser, true, true, NULL,
+						    NULL_TREE, NULL, NULL, NULL);
   if (vec_safe_length (expr_list) == 1)
     {
       /* Just return the expression, remove a level of
@@ -11948,7 +12067,7 @@  c_parser_oacc_wait_list (c_parser *parser, location_t clause_loc, tree list)
   if (!parens.require_open (parser))
     return list;
 
-  args = c_parser_expr_list (parser, false, true, NULL, NULL, NULL, NULL);
+  args = c_parser_expr_list (parser, false, true, NULL, NULL, NULL, NULL, NULL);
   args_tree = build_tree_list_vec (args);
 
   for (t = args_tree; t; t = TREE_CHAIN (t))
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index dae2979d482..eda18618452 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -665,7 +665,7 @@  extern struct c_expr parser_build_unary_op (location_t, enum tree_code,
     					    struct c_expr);
 extern struct c_expr parser_build_binary_op (location_t,
     					     enum tree_code, struct c_expr,
-					     struct c_expr);
+					     struct c_expr, bool = true);
 extern tree build_conditional_expr (location_t, tree, bool, tree, tree,
 				    location_t, tree, tree, location_t);
 extern tree build_compound_expr (location_t, tree, tree);
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index d4e12eb93d1..2e439c43b91 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -2146,14 +2146,30 @@  perform_integral_promotions (tree exp)
    In addition, manifest constants symbols are replaced by their values.  */
 
 tree
-default_conversion (tree exp)
+default_conversion (tree exp, bool read_p /* = true */)
 {
   tree orig_exp;
   tree type = TREE_TYPE (exp);
   enum tree_code code = TREE_CODE (type);
   tree promoted_type;
 
-  mark_exp_read (exp);
+  if (!read_p)
+    {
+      /* Set READ_P for expressions that don't refer to declared objects.  */
+      tree tmp = exp;
+      STRIP_NOPS (tmp);
+      if (TREE_CODE (tmp) == ADDR_EXPR)
+	tmp = TREE_OPERAND (tmp, 0);
+
+      while (handled_component_p (tmp))
+	tmp = TREE_OPERAND (tmp, 0);
+
+      if (!DECL_P (tmp))
+	read_p = true;
+    }
+
+  if (read_p)
+    mark_exp_read (exp);
 
   /* Functions and arrays have been converted during parsing.  */
   gcc_assert (code != FUNCTION_TYPE);
@@ -3718,7 +3734,8 @@  char_type_p (tree type)
 
 struct c_expr
 parser_build_binary_op (location_t location, enum tree_code code,
-			struct c_expr arg1, struct c_expr arg2)
+			struct c_expr arg1, struct c_expr arg2,
+			bool read_p /* = true */)
 {
   struct c_expr result;
 
@@ -3732,7 +3749,7 @@  parser_build_binary_op (location_t location, enum tree_code code,
                 : TREE_TYPE (arg2.value));
 
   result.value = build_binary_op (location, code,
-				  arg1.value, arg2.value, true);
+				  arg1.value, arg2.value, true, read_p);
   result.original_code = code;
   result.original_type = NULL;
 
@@ -11354,7 +11371,7 @@  build_vec_cmp (tree_code code, tree type,
 
 tree
 build_binary_op (location_t location, enum tree_code code,
-		 tree orig_op0, tree orig_op1, bool convert_p)
+		 tree orig_op0, tree orig_op1, bool convert_p, bool read_p)
 {
   tree type0, type1, orig_type0, orig_type1;
   tree eptype;
@@ -11459,8 +11476,8 @@  build_binary_op (location_t location, enum tree_code code,
   if (convert_p
       && VECTOR_TYPE_P (TREE_TYPE (op0)) == VECTOR_TYPE_P (TREE_TYPE (op1)))
     {
-      op0 = default_conversion (op0);
-      op1 = default_conversion (op1);
+      op0 = default_conversion (op0, read_p);
+      op1 = default_conversion (op1, read_p);
     }
 
   orig_type0 = type0 = TREE_TYPE (op0);
diff --git a/gcc/calls.c b/gcc/calls.c
index 51ad55f15a9..f8463bf98ba 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -52,6 +52,8 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-ssa-strlen.h"
 #include "intl.h"
 #include "stringpool.h"
+#include "hash-map.h"
+#include "hash-traits.h"
 #include "attribs.h"
 #include "builtins.h"
 #include "gimple-fold.h"
@@ -1862,6 +1864,109 @@  maybe_complain_about_tail_call (tree call_expr, const char *reason)
   error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason);
 }
 
+/* Used to define rdwr_map below.  */
+struct rdwr_access_hash: int_hash<int, -1> { };
+
+/* A mapping between argument number corresponding to attribute read_only,
+   write_only, or read_write operand and the read/write specification.  */
+typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
+
+/* Initialize a mapping for a call to function FNDECL declared with
+   attributes read_only, write_only, or read_write.  Each attribute
+   operand inserts one entry into the mapping with the operand number
+   as the key.  */
+
+static void
+init_attr_rdwr_indices (rdwr_map *rwm, tree fndecl)
+{
+  if (!fndecl)
+    return;
+
+  /* Iterate over arguments to functions declared with attribute
+     read_only or read_write and diagnose those that are not
+     initialized.  */
+  const attr_access::Kind access_kinds[] = {
+    attr_access::read_only, attr_access::read_write, attr_access::write_only
+  };
+
+  for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i)
+    {
+      attr_access acc = { };
+      acc.kind = access_kinds[i];
+      while (get_attr_access_ptr_and_size (TREE_TYPE (fndecl), &acc))
+	{
+	  rwm->put (acc.ptrarg, acc);
+	  if (acc.ptrarg != UINT_MAX)
+	    rwm->put (acc.sizarg, acc);
+	}
+    }
+}
+
+/* Iterate over read-only, read-write, and write-only arguments
+   and diagnose past-the-end accesses in the function call EXP.  */
+
+static void
+maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
+{
+  for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it)
+    {
+      std::pair<int, attr_access> access = *it;
+
+      /* Get the function call arguments corresponding to the attribute's
+	 first and (optionally) second operands.  When both operands have
+	 been specified there will be two entries in *RWM, one for each.
+	 They are cross-referenced by their respective argument numbers
+	 in ACCESS.PTRARG and ACCESS.SIZARG.  */
+      int ptridx = access.second.ptrarg;
+      int sizidx = access.second.sizarg;
+
+      gcc_assert (ptridx != -1);
+      gcc_assert (access.first == ptridx || access.first == sizidx);
+
+      /* The pointer is set to null for the entry corresponding to
+	 the size argument.  Skip it.  It's handled when the entry
+	 corresponding to the pointer argument comes up.  */
+      if (!access.second.ptr)
+	continue;
+
+      /* If only one attribute operand was specified the size isn't
+	 available and so continue to the next one.  */
+
+      /* FIXME: determine the size using some heuristic -- e.g., for
+	 complete types, by assuming it's equal to the size of the type
+	 the pointer points to.  */
+      if (sizidx == -1)
+	continue;
+
+      tree ptr = access.second.ptr;
+      tree size = rwm->get (sizidx)->size;
+      tree objsize = compute_objsize (ptr, 0);
+
+      tree srcsize;
+      if (access.second.kind == attr_access::write_only)
+	{
+	  /* For a write-only argument there is no source.  */
+	  srcsize = NULL_TREE;
+	}
+      else
+	{
+	  /* For read-only and read-write attributes also set the source
+	     size.  */
+	  srcsize = objsize;
+	  if (access.second.kind == attr_access::read_only)
+	    {
+	      /* For a read-only attribute there is no destination so
+		 clear OBJSIZE.  This emits "reading N bytes" kind of
+		 diagnostics instead of the "writing N bytes" kind.  */
+	      objsize = NULL_TREE;
+	    }
+	}
+
+      check_access (exp, NULL_TREE, NULL_TREE, size, /*maxread=*/ NULL_TREE,
+		    srcsize, objsize);
+    }
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1978,6 +2083,11 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
   /* Array for up to the two attribute alloc_size arguments.  */
   tree alloc_args[] = { NULL_TREE, NULL_TREE };
 
+  /* Map of attribute read_only, write_only, or read_write specifications
+     for function arguments.  */
+  rdwr_map rdwr_idx;
+  init_attr_rdwr_indices (&rdwr_idx, fndecl);
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -2218,6 +2328,22 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	alloc_args[0] = args[i].tree_value;
       else if (argpos == alloc_idx[1])
 	alloc_args[1] = args[i].tree_value;
+
+      /* Save the actual argument that corresponds to the access attribute
+	 operand for later processing.  */
+      if (attr_access *access = rdwr_idx.get (argpos))
+	{
+	  if (POINTER_TYPE_P (type))
+	    {
+	      access->ptr = args[i].tree_value;
+	      gcc_assert (access->size == NULL_TREE);
+	    }
+	  else
+	    {
+	      access->size = args[i].tree_value;
+	      gcc_assert (access->ptr == NULL_TREE);
+	    }
+	}
     }
 
   if (alloc_args[0])
@@ -2230,6 +2356,9 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
   /* Detect passing non-string arguments to functions expecting
      nul-terminated strings.  */
   maybe_warn_nonstring_arg (fndecl, exp);
+
+  /* Check read_only, write_only, and read_write arguments.  */
+  maybe_warn_rdwr_sizes (&rdwr_idx, exp);
 }
 
 /* Update ARGS_SIZE to contain the total size for the argument block.
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index f427c4f4d3e..917e8c3c917 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -2191,7 +2191,7 @@  cp_default_conversion (tree exp, tsubst_flags_t complain)
 /* C version.  */
 
 tree
-default_conversion (tree exp)
+default_conversion (tree exp, bool /*read_p*/)
 {
   return cp_default_conversion (exp, tf_warning_or_error);
 }
@@ -4309,7 +4309,7 @@  enum_cast_to_int (tree op)
 /* For the c-common bits.  */
 tree
 build_binary_op (location_t location, enum tree_code code, tree op0, tree op1,
-		 bool /*convert_p*/)
+		 bool /*convert_p*/, bool /*read_p*/)
 {
   return cp_build_binary_op (location, code, op0, op1, tf_warning_or_error);
 }
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 64fccfe9b87..11ff70a8e91 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3850,6 +3850,86 @@  At present, a declaration to which @code{weakref} is attached can
 only be @code{static}.
 
 
+@item read_only
+@itemx read_only
+@itemx read_only (@var{arg-index})
+@itemx read_only (@var{ref-index}, @var{size-index})
+@cindex @code{read_only} function attribute
+The @code{read_only} attribute specifies that a function whose by-reference
+arguments of @code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to read
+from the objects referenced by the argument but not write to them.  Passing
+in the address of an uninitialized object is undefined.
+
+For example, the declaration below indicates that the @code{get_time} function
+only reads the @code{struct tm} object pointed-to by the argument but doesn't
+modify it.  Casting away the constness and modifying the argument is undefined.
+This information may be relied on to emit more efficient code at the call site,
+by one of the @option{-Wunused} options.  Built-in functions such as
+@code{strlen} declare their const pointer arguments with the @code{read_only}
+attribute.
+
+@smallexample
+time_t get_time (const struct tm *) __attribute__ ((read_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may read.  Passing
+in an array with fewer elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function reads as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+int sum (const int *, unsigned) __attribute__ ((read_only (1, 2)));
+@end smallexample
+
+
+@item read_write
+@itemx read_write
+@itemx read_write (@var{arg-index})
+@itemx read_write (@var{ref-index}, @var{size-index})
+@cindex @code{read_write} function attribute
+The @code{read_write} attribute specifies that a function whose by-reference
+arguments denoted by @var{ref-index} (or all such arguments if no
+@var{ref-index} is specified) are used to both read from the objects
+referenced by the argument and to write to them.  Passing in the address
+of an uninitialized object is undefined.
+
+
+@item write_only
+@itemx write_only
+@itemx write_only (@var{arg-index})
+@itemx write_only (@var{ref-index}, @var{size-index})
+@cindex @code{write_only} function attribute
+The @code{write_only} attribute specifies that a function whose by-reference
+arguments of non-@code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to write
+into the objects referenced by the argument but not read from it.  For example,
+the declaration below indicates that the @code{init_tm} function only writes
+to the @code{struct tm} object pointed-to by the argument but doesn't read
+from it.  This information may be relied on to emit more efficient code at
+the call site, and diagnose initialized but otherwise unused objects by one
+of the @option{-Wunused} options.  Built-in functions such as @code{memset}
+and @code{strcpy} declare their non-const pointer argument with
+the @code{write_only} attribute.
+
+@smallexample
+int init_tm (struct tm *) __attribute__ ((write_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may write.  Passing
+in an array with fewer elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function writes as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+void init (int *, unsigned, int) __attribute__ ((write_only (1, 2)));
+@end smallexample
+
+
 @end table
 
 @c This is the end of the target-independent attribute table
diff --git a/gcc/gimple.h b/gcc/gimple.h
index cf1f8da5ae2..4d850a088cf 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -24,6 +24,8 @@  along with GCC; see the file COPYING3.  If not see
 
 #include "tree-ssa-alias.h"
 #include "gimple-expr.h"
+#include "function.h"
+#include "basic-block.h"
 
 typedef gimple *gimple_seq_node;
 
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
index 4281e3b5a8e..a0c1887c1a9 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
@@ -1,7 +1,7 @@ 
 /* Test -Wsizeof-pointer-memaccess warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument" } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat" { target c } } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat -Wno-stringop-overflow" { target c } } */
 /* { dg-require-effective-target alloca } */
 
 typedef __SIZE_TYPE__ size_t;
@@ -30,7 +30,7 @@  void foo (void **);
 void
 f1 (void *x)
 {
-  struct A a, *pa1 = &a;
+  struct A a = { }, *pa1 = &a;
   TA *pa2 = &a;
   PA pa3 = &a;
   PTA pa4 = &a;
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
index d9a1555e7ce..08ef1695080 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
@@ -484,5 +484,6 @@  f4 (char *x, char **y, int z, char w[64])
   stpncpy (x, s3, sizeof (s3));             /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
 }
 
+/* { dg-prune-output "-Wunused-but-set-variable" } */
 /* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
 /* { dg-prune-output "\[\n\r\]*reading\[\n\r\]*" } */
diff --git a/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c b/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
index 87f5ef9d171..f7f70712baf 100644
--- a/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
+++ b/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
@@ -9,3 +9,5 @@  int main(int argc, char *argv[])
     float y[argc];
     return 0 == __builtin_memcpy(y, x, argc * sizeof(*x));
 }
+
+/* { dg-prune-output "-Wunused-but-set-variable" } */
diff --git a/gcc/testsuite/gcc.dg/Wunused-but-set-var.c b/gcc/testsuite/gcc.dg/Wunused-but-set-var.c
new file mode 100644
index 00000000000..87a0d947432
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wunused-but-set-var.c
@@ -0,0 +1,225 @@ 
+/* PR c/80806 - gcc does not warn if local array is memset only
+   Verify that attribute write_only implicitly applied to common
+   built-ins or explicitly specified on user-defined functions is
+   used by -Wunused-but-set-variable and -Wunused-but-set-parameter.
+   { dg-do compile }
+   { dg-options "-Wunused-but-set-variable -Wunused-but-set-parameter" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void bcopy (const void*, void*, size_t);
+void bzero (void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+char* strcat (char*, const char*);
+char* strcpy (char*, const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void wronly_1 (void*) WRONLY (1);
+
+struct MemArrays
+{
+  int a[2], b[3][3];
+};
+
+enum { N = sizeof (int) };
+
+void test_bcopy (int a,   /* { dg-warning "-Wunused-but-set-parameter" } */
+		 void *p,
+		 struct MemArrays *pma)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &a, N);
+  bcopy (p, &i, N);
+
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &arr, N);
+  bcopy (p, &arr, sizeof arr);
+  bcopy (p, arr, N);
+  bcopy (p, arr + 0, N);
+  bcopy (p, arr + 1, N);
+  bcopy (p, &arr[0], N);
+  bcopy (p, &arr[1], N);
+  bcopy (p, &arr[2] + 1, N);
+
+  struct MemArrays s;     /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, s.a, N);
+  bcopy (p, s.a, sizeof s.a);
+  bcopy (p, &s.a[0], N);
+  bcopy (p, &s.a[1], N);
+  bcopy (p, &s.b[0], N);
+  bcopy (p, &s.b[0] + 1, N);
+  bcopy (p, &s.b[0][0], N);
+  bcopy (p, &s.b[0][0] + 0, N);
+  bcopy (p, &s.b[0][0] + 1, N);
+  bcopy (p, &s.b[0][1], N);
+  bcopy (p, &s.b[1][1] + 1, N);
+
+  struct MemArrays t;
+
+  bcopy (p, &t.b[0] + 1, 1);
+  bcopy (&t, p, 4);
+
+  bcopy (p, pma->a, N);
+}
+
+
+void test_bzero (int a)   /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bzero (&a, N);
+  bzero (&a + 0, N);
+  bzero (&i, N);
+  bzero (&i + 0, N);
+
+  bzero (arr, N);
+  bzero (arr + 0, N);
+  bzero (arr + 1, N);
+  bzero (&arr[0] + 1, N);
+  bzero (&arr[0], N);
+  bzero (&arr[1], N);
+}
+
+void test_memset (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memset (&a, 0, N);
+  memset (&i, 0, N);
+  memset (&arr, 0, N);
+}
+
+void test_memcpy (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		  const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memcpy (&a, p, N);
+  memcpy (&i, p, N);
+  memcpy (&arr, p, N);
+}
+
+void test_strcat (const char *s)
+{
+  char c = '\0';
+  char arr[8] = "";
+
+  strcat (&c, "");
+  strcat (arr, s);
+}
+
+void test_strcpy (const char *s)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strcpy (&c, "");
+  strcpy (&d, "");
+  strcpy (arr, s);
+  strcpy (arr2, s);
+}
+
+void test_strncpy (const char *s, unsigned n)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strncpy (&c, "", 1);
+  strncpy (&d, "", n);
+  strncpy (arr, s, n);
+  strncpy (arr2, s, n);
+}
+
+/* Verify that passing a local pointer initialized to the result of
+   malloc() (anything else should work as well) to free() on doesn't
+   trigger a warning because the pointer value is obviously read.  */
+
+void test_malloc_and_free (size_t n)
+{
+  void *q = malloc (n);
+  free (q);
+}
+
+/* Verify that setting a local variable to a copy of an argument and
+   calling free() on the copy doesn't trigger a warning.  */
+
+void test_free_argcpy (void *p)
+{
+  void *q = p;
+  free (q);
+}
+
+/* Similar to the free test above, verify that setting a local variable
+   to a copy of an argument and calling free() on the copy doesn't trigger
+   a warning.  */
+
+void test_memset_argcpy (void *p, size_t n)
+{
+  void *q = p;
+  memset (q, 0, n);
+}
+
+void test_usrdef (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  wronly_1 (&a);
+  wronly_1 (&i);
+  wronly_1 (&arr);
+
+  wronly_1 (arr);
+  wronly_1 (arr + 1);
+  wronly_1 (&arr[1]);
+}
+
+/* Verify that using a pointer to a function works.  */
+typedef struct S
+{
+  void (*pf)(void*) WRONLY (1);
+  struct {
+    struct {
+      void (*pf)(void*) WRONLY (1);
+    } s2;
+  } s1;
+} S;
+
+void test_funcptr (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		   S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->pf (&a);
+  ps->pf (&i);
+  ps->pf (&arr);
+}
+
+void test_funcptr2 (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		    S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->s1.s2.pf (&a);
+  ps->s1.s2.pf (&i);
+  ps->s1.s2.pf (&arr);
+}
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c
index 88a77715805..66fd083f9bc 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c
@@ -32,5 +32,6 @@  test (void)
   p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5);
   strcpy (p, "World");
   strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
-}
 
+  (void)&p;   /* suppress -Wunused-but-set-variable */
+}
diff --git a/gcc/testsuite/gcc.dg/attr-read-only-2.c b/gcc/testsuite/gcc.dg/attr-read-only-2.c
new file mode 100644
index 00000000000..53a996fbdd1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-read-only-2.c
@@ -0,0 +1,85 @@ 
+/* Test to verify that attributes read_only and read_write are used
+   by -Wuninitialized to issue warnings.
+   { dg-do compile }
+   { dg-options "-Wuninitialized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern char* memcpy (void* restrict, const void* restrict, size_t);
+extern char* memmove (void*, const void*, size_t);
+
+extern char* stpcpy (char* restrict, const char* restrict);
+extern char* stpncpy (char* restrict, const char* restrict, size_t);
+extern char* strcpy (char* restrict, const char* restrict);
+extern char* strncpy (char* restrict, const char* restrict, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define RDONLY(...)  __attribute__ ((read_only (__VA_ARGS__)))
+
+void sink (void*);
+
+/* Due to PR 81809 only the first uninitialized read in a function
+   is diagnosed.  The subsequent ones are not because the variable's
+   address is considered to have escaped.  */
+
+void test_memcpy (char *d)
+{
+  char a7[7];
+  memcpy (d, a7, sizeof a7);  /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void* test_memmove (void)
+{
+  char a7[7];
+  enum { n = sizeof a7 - 1 };
+  memmove (a7, a7 + 1, n);    /* { dg-warning ".a7. is used uninitialized" } */
+  sink (a7);
+}
+
+void test_strcpy (char *d)
+{
+  char a7[7];
+  strcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void test_stpncpy (char *d)
+{
+  char a7[7];
+  stpncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void test_strncpy (char *d)
+{
+  char a7[7];
+  strncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+
+int rdonly_pv_1 (const void*) RDONLY (1);
+
+void test_rdonly_1 (void)
+{
+  int i;
+  rdonly_pv_1 (&i);           /* { dg-warning ".i. is used uninitialized" } */
+}
+
+void test_rdonly_2 (void)
+{
+  int a[2];
+  rdonly_pv_1 (a);            /* { dg-warning ".a. is used uninitialized" } */
+}
+
+
+int rdonly_pv_s_1_2 (const void*, size_t) RDONLY (1, 2);
+
+void test_rdonly_1_2 (void)
+{
+  int a[4];
+  a[2] = 0;
+
+  enum { n = sizeof a[1] };
+  rdonly_pv_s_1_2 (a + 1, n); /* { dg-warning ".a. is used uninitialized" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/attr-read-only.c b/gcc/testsuite/gcc.dg/attr-read-only.c
new file mode 100644
index 00000000000..a0820b80d42
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-read-only.c
@@ -0,0 +1,63 @@ 
+/* Test to verify the handling of attribute read_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define RDONLY(...)  __attribute__ ((read_only (__VA_ARGS__)))
+
+int  __attribute__ ((read_only))
+rdonly_v_all (void);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_i_all (int);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_pi_all (const int*);
+
+int  __attribute__ ((read_only))
+rdonly_pv_pi_all (const void*, const int*);
+
+int  __attribute__ ((read_only))
+rdonly_i_pc_i_pd_all (int, const char*, int, const double*);
+
+int RDONLY (1)
+rdonly_pcv_1 (const void*);
+int RDONLY (2)
+rdonly_i_pcv_2 (int, const void*);
+int RDONLY (3)
+rdonly_i_i_pcv_3 (int, int, const void*);
+
+int RDONLY (0 + 1)
+rdonly_pcv_0p1 (const void*);
+
+int RDONLY (2 - 1)
+rdonly_pcv_2m1 (const void*);
+
+int RDONLY (1, 1)
+rdonly_pv_pi_1_1 (const void*, const int*);      /* { dg-error "attribute .read_only\\(1, 1\\). second operand references non-integer argument type .const void *." } */
+
+int  RDONLY (1, 2)
+rdonly_pcv_pc_1_2 (const void*, char*);   /* { dg-error "attribute .read_only\\(1, 2\\). second operand references non-integer argument type .char *." } */
+
+int  RDONLY (2, 1)
+rdonly_pcd_pcv_2_1 (const double*, const void*);   /* { dg-error "attribute .read_only\\(2, 1\\). second operand references non-integer argument type .const double *." } */
+
+int __attribute__ ((read_only))
+rdonly_pcv_pcv_all (const void*, const void*);
+
+int RDONLY (2, 2)
+rdonly_pi_pcv_2_2 (int*, const void*);   /* { dg-error "second operand references non-integer argument type .const void *." } */
+
+int RDONLY (4)
+rdonly_i_i_i_4 (int, int, int);   /* { dg-error "attribute .read_only\\(4\\). operand exceeds number of arguments 3" } */
+
+int RDONLY (1)
+rdonly_i_1 (int);   /* { dg-error "attribute .read_only\\(1\\). references non-pointer argument type .int." } */
+
+int RDONLY (2)
+rdonly_i_pc (int, char*);   /* { dg-warning "attribute .read_only\\(2\\). operand references non-.const. argument type .char \\\*." } */
+
+int RDONLY (-1)
+rdonly_pcv_m1 (const void*);   /* { dg-error "attribute .read_only\\(-1\\). invalid operand" } */
+
+int RDONLY ("blah")
+rdonly_pcv_str (const void*);   /* { dg-error "attribute .read_only\\(\"blah\"\\). invalid operand" } */
diff --git a/gcc/testsuite/gcc.dg/attr-write-only-2.c b/gcc/testsuite/gcc.dg/attr-write-only-2.c
new file mode 100644
index 00000000000..8ff0ad92eb4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-write-only-2.c
@@ -0,0 +1,192 @@ 
+/* Test to verify that attribute write_only is recognized by
+   -Wunused-but-set-variable.
+   { dg-do compile }
+   { dg-options "-Wunused-but-set-variable -Wunused-but-set-parameter" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void bcopy (const void*, void*, size_t);
+void bzero (void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+char* strcat (char*, const char*);
+char* strcpy (char*, const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void wronly_1 (void*) WRONLY (1);
+
+enum { N = sizeof (int) };
+
+void test_bcopy (int a,   /* { dg-warning "-Wunused-but-set-parameter" } */
+		 const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &a, N);
+  bcopy (p, &i, N);
+
+  bcopy (p, &arr, N);
+  bcopy (p, arr, N);
+  bcopy (p, arr + 0, N);
+  bcopy (p, arr + 1, N);
+  bcopy (p, &arr[0], N);
+  bcopy (p, &arr[1], N);
+}
+
+void test_bzero (int a)   /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bzero (&a, N);
+  bzero (&a + 0, N);
+  bzero (&i, N);
+  bzero (&i + 0, N);
+
+  bzero (arr, N);
+  bzero (arr + 0, N);
+  bzero (arr + 1, N);
+  bzero (&arr[0] + 1, N);
+  bzero (&arr[0], N);
+  bzero (&arr[1], N);
+}
+
+void test_memset (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memset (&a, 0, N);
+  memset (&i, 0, N);
+  memset (&arr, 0, N);
+}
+
+void test_memcpy (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		  const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memcpy (&a, p, N);
+  memcpy (&i, p, N);
+  memcpy (&arr, p, N);
+}
+
+void test_strcat (const char *s)
+{
+  char c = '\0';
+  char arr[8] = "";
+
+  strcat (&c, "");
+  strcat (arr, s);
+}
+
+void test_strcpy (const char *s)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strcpy (&c, "");
+  strcpy (&d, "");
+  strcpy (arr, s);
+  strcpy (arr2, s);
+}
+
+void test_strncpy (const char *s, unsigned n)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strncpy (&c, "", 1);
+  strncpy (&d, "", n);
+  strncpy (arr, s, n);
+  strncpy (arr2, s, n);
+}
+
+/* Verify that passing a local pointer initialized to the result of
+   malloc() (anything else should work as well) to free() on doesn't
+   trigger a warning because the pointer value is obviously read.  */
+
+void test_malloc_and_free (size_t n)
+{
+  void *q = malloc (n);
+  free (q);
+}
+
+/* Verify that setting a local variable to a copy of an argument and
+   calling free() on the copy doesn't trigger a warning.  */
+
+void test_free_argcpy (void *p)
+{
+  void *q = p;
+  free (q);
+}
+
+/* Similar to the free test above, verify that setting a local variable
+   to a copy of an argument and calling free() on the copy doesn't trigger
+   a warning.  */
+
+void test_memset_argcpy (void *p, size_t n)
+{
+  void *q = p;
+  memset (q, 0, n);
+}
+
+void test_usrdef (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  wronly_1 (&a);
+  wronly_1 (&i);
+  wronly_1 (&arr);
+
+  wronly_1 (arr);
+  wronly_1 (arr + 1);
+  wronly_1 (&arr[1]);
+}
+
+/* Verify that using a pointer to a function works.  */
+typedef struct S
+{
+  void (*pf)(void*) WRONLY (1);
+  struct {
+    struct {
+      void (*pf)(void*) WRONLY (1);
+    } s2;
+  } s1;
+} S;
+
+void test_funcptr (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		   S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->pf (&a);
+  ps->pf (&i);
+  ps->pf (&arr);
+}
+
+void test_funcptr2 (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		    S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->s1.s2.pf (&a);
+  ps->s1.s2.pf (&i);
+  ps->s1.s2.pf (&arr);
+}
diff --git a/gcc/testsuite/gcc.dg/attr-write-only.c b/gcc/testsuite/gcc.dg/attr-write-only.c
new file mode 100644
index 00000000000..27db9c17ce7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-write-only.c
@@ -0,0 +1,51 @@ 
+/* Test to verify the handling of attribute write_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void  __attribute__ ((write_only))
+wronly_v_all (void);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void  __attribute__ ((write_only))
+wronly_i_all (int);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void wronly_v_1 (void*) WRONLY (1);
+void wronly_iv_2 (int, void*) WRONLY (2);
+void wronly_iiv_3 (int, int, void*) WRONLY (3);
+
+void wronly_v_0p1 (void*) WRONLY (0 + 1);
+void wronly_v_2m1 (void*) WRONLY (2 - 1);
+
+void WRONLY (1, 1)
+wronly_v_1_1 (void*);      /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (1, 2)
+wronly_vv_1_2 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 2\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (2, 1)
+wronly_vv_2_1 (void*, void*);   /* { dg-error "attribute .write_only\\(2, 1\\). second operand references non-integer argument type .void *." } */
+
+void  __attribute__ ((write_only))
+wronly_vv_all (void*, void*);
+
+void  WRONLY (1, 1)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void WRONLY (2, 2)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "second operand references non-integer argument type .void *." } */
+
+void WRONLY (4)
+wronly_4_exceed (int, int, int);   /* { dg-error "attribute .write_only\\(4\\). operand exceeds number of arguments 3" } */
+
+void WRONLY (1)
+wronly_1_i (int);   /* { dg-error "attribute .write_only\\(1\\). references non-pointer argument type .int." } */
+
+void WRONLY (2)
+wronly_2_cst (int, const char*);   /* { dg-error "attribute .write_only\\(2\\). operand references .const.-qualified argument type .const char *." } */
+
+void WRONLY (-1)
+wronly_m1_i (void*);    /* { dg-error "attribute .write_only\\(-1\\). invalid operand" } */
+
+void WRONLY ("blah")
+wronly_str_i (void*);   /* { dg-error "attribute .write_only\\(\"blah\"\\). invalid operand" } */
diff --git a/gcc/testsuite/gcc.dg/nonnull-3.c b/gcc/testsuite/gcc.dg/nonnull-3.c
index 6f7bc4f4295..38541128d6e 100644
--- a/gcc/testsuite/gcc.dg/nonnull-3.c
+++ b/gcc/testsuite/gcc.dg/nonnull-3.c
@@ -10,8 +10,8 @@  void
 foo (void *p, char *s)
 {
   __builtin_bzero (NULL, 0);
-  __builtin_bcopy (NULL, p, 0);
-  __builtin_bcopy (p, NULL, 0);
+  __builtin_bcopy (NULL, p, 0);  /* { dg-warning "null" "null pointer check" } */
+  __builtin_bcopy (p, NULL, 0);  /* { dg-warning "null" "null pointer check" } */
   __builtin_bcmp (NULL, p, 0);
   __builtin_bcmp (p, NULL, 0);
   __builtin_index (NULL, 16);  /* { dg-warning "null" "null pointer check" } */
diff --git a/gcc/testsuite/gcc.dg/pr40340-2.c b/gcc/testsuite/gcc.dg/pr40340-2.c
index ea76e10082d..473a27c01b9 100644
--- a/gcc/testsuite/gcc.dg/pr40340-2.c
+++ b/gcc/testsuite/gcc.dg/pr40340-2.c
@@ -1,6 +1,6 @@ 
-/* PR middle-end/40340 */
+/* PR middle-end/40340 - Fortification warning no longer emitted in inlines */
 /* { dg-do compile } */
-/* { dg-options "-O2 -Wall -Wno-system-headers -fno-tree-dse" } */
+/* { dg-options "-O2 -Wall -Wno-system-headers -Wno-unused -fno-tree-dse" } */
 
 #include "pr40340.h"
 
diff --git a/gcc/testsuite/gcc.dg/pr78768.c b/gcc/testsuite/gcc.dg/pr78768.c
index 72ac3f87129..f931a2a6868 100644
--- a/gcc/testsuite/gcc.dg/pr78768.c
+++ b/gcc/testsuite/gcc.dg/pr78768.c
@@ -11,5 +11,7 @@  int main (void)
 
   __builtin_sprintf (d, "%32s", "x");   /* { dg-warning "directive writing 32 bytes into a region of size 12" "-Wformat-overflow" } */
 
+  (void)&d;   /* Suppress -Wunused-but-set-variable.  */
+
   return 0;
 }
diff --git a/gcc/testsuite/gcc.dg/pr79715.c b/gcc/testsuite/gcc.dg/pr79715.c
index 0f0f90f7122..3f7b38db7c3 100644
--- a/gcc/testsuite/gcc.dg/pr79715.c
+++ b/gcc/testsuite/gcc.dg/pr79715.c
@@ -9,6 +9,8 @@  void f (const char *s)
   char *p = __builtin_malloc (n);
   __builtin_memcpy (p, s, n);
   __builtin_free (p);
+
+  (void)&p;   /* Suppress -Wunused-but-set-variable.  */
 }
 
 void g (const char *s)
@@ -17,6 +19,8 @@  void g (const char *s)
   char *p = __builtin_malloc (n);
   __builtin_strcpy (p, s);
   __builtin_free (p);
+
+  (void)&p;   /* Suppress -Wunused-but-set-variable.  */
 }
 
 /* { dg-final { scan-tree-dump-not "free" "optimized" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
index bf5072e955c..195bf34dde7 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
@@ -129,6 +129,7 @@  ELIM (5 ==, ga4, "123", "%.3s%.2s", ga4, ga4);
     char a1[N1], a2[N2];				\
     memcpy (a1, init1, sizeof (init1) - 1);		\
     memcpy (a2, init2, sizeof (init2) - 1);		\
+    (void)a1; (void)a2;					\
     const int res = snprintf (0, 0, fmt, __VA_ARGS__);	\
     VERIFY_ELIM (expect res);				\
   } typedef void dummy_typedef
diff --git a/gcc/testsuite/gcc.dg/uninit-builtin.c b/gcc/testsuite/gcc.dg/uninit-builtin.c
new file mode 100644
index 00000000000..469aeaf6a71
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/uninit-builtin.c
@@ -0,0 +1,207 @@ 
+/* Test to verify that -Wuninitialized detects uninitialized reads
+   by built-in functions.
+   { dg-do compile }
+   { dg-options "-O2 -Wuninitialized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void free (void*);
+
+void sink (void*);
+
+/* Due to PR 81809 only the first uninitialized read in a function
+   is diagnosed.  The subsequent ones are not because the variable's
+   address is considered to have escaped.  */
+
+int test_memchr (int c)
+{
+  extern char* memchr (const void*, int, size_t);
+
+  char a3[3];
+  char *p;
+  enum { n = sizeof a3 };
+  p = memchr (a3, c, n);      /* { dg-warning ".a3. is used uninitialized" "memchr" } */
+  return p != 0;
+}
+
+void test_memcpy (char *d)
+{
+  extern char* memcpy (void* restrict, const void* restrict, size_t);
+
+  char a5[5];
+  enum { n = sizeof a5 };
+  memcpy (d, a5, n);          /* { dg-warning ".a5. is used uninitialized" "memcpy" } */
+}
+
+void test_memmove (void)
+{
+  extern char* memmove (void*, const void*, size_t);
+
+  char a7[7];
+  enum { n = sizeof a7 - 1 };
+  memmove (a7, a7 + 1, n);    /* { dg-warning ".a7. is used uninitialized" "memmove" } */
+  sink (a7);
+}
+
+int test_strcat_1 (const char *s)
+{
+  extern char* strcat (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcat (a7, s);             /* { dg-warning ".a7. is used uninitialized" "strcat 1" } */
+  return a7[0];
+}
+
+void test_strcat_2 (char *d)
+{
+  extern char* strcat (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcat (d, a7);             /* { dg-warning ".a7. is used uninitialized" "strcat 2" } */
+}
+
+int test_strchr (const char *s, int c)
+{
+  extern char* strchr (const char*, int);
+
+  char a7[7];
+  s = strchr (a7, c);         /* { dg-warning ".a7. is used uninitialized" "strchr" } */
+  return s != 0;
+}
+
+char* test_strdup (void)
+{
+  extern char* strdup (const char*);
+
+  char a7[7];
+  char *s;
+  s = strdup (a7);            /* { dg-warning ".a7. is used uninitialized" "strdup" } */
+  return s;
+}
+
+int test_strstr_1 (const char *s)
+{
+  extern char* strstr (const char*, const char*);
+
+  char a7[7];
+  s = strstr (a7, s);         /* { dg-warning ".a7. is used uninitialized" "strstr 1" } */
+  return s != 0;
+}
+
+int test_strstr_2 (const char *s)
+{
+  extern char* strstr (const char*, const char*);
+
+  char a7[7];
+  s = strstr (s, a7);         /* { dg-warning ".a7. is used uninitialized" "strstr 2" } */
+  return s != 0;
+}
+
+void test_strcpy (char *d)
+{
+  extern char* strcpy (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" "strcpy" } */
+}
+
+void test_stpcpy (char *d)
+{
+  extern char* stpcpy (char* restrict, const char* restrict);
+
+  char a7[7];
+  stpcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" "stpcpy" } */
+}
+
+void test_stpncpy (char *d)
+{
+  extern char* stpncpy (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  stpncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" "stpncpy" } */
+}
+
+int test_strcmp_1 (const char *s)
+{
+  extern int strcmp (const char*, const char*);
+
+  char a7[7];
+  int i;
+  i = strcmp (a7, s);         /* { dg-warning ".a7. is used uninitialized" "strcmp" } */
+  return i;
+}
+
+int test_strcmp_2 (const char *s)
+{
+  extern int strcmp (const char*, const char*);
+
+  char a7[7];
+  int i;
+  i = strcmp (s, a7);         /* { dg-warning ".a7. is used uninitialized" "strcmp" } */
+  return i;
+}
+
+int test_strncat_1 (const char *s, size_t n)
+{
+  extern char* strncat (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  s = strncat (a7, s, n);     /* { dg-warning ".a7. is used uninitialized" "strncat 1" } */
+  return s != 0;
+}
+
+int test_strncat_2 (char *d, size_t n)
+{
+  extern char* strncat (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  d = strncat (d, a7, n);     /* { dg-warning ".a7. is used uninitialized" "strncat 2" } */
+  return d != 0;
+}
+
+int test_strncmp_1 (const char *s, size_t n)
+{
+  extern int strncmp (const char*, const char*, size_t);
+
+  char a7[7];
+  int i;
+  i = strncmp (a7, s, n);     /* { dg-warning ".a7. is used uninitialized" "strncmp 1" } */
+  return i;
+}
+
+int test_strncmp_2 (const char *s, size_t n)
+{
+  extern int strncmp (const char*, const char*, size_t);
+
+  char a7[7];
+  int i;
+  i = strncmp (s, a7, n);     /* { dg-warning ".a7. is used uninitialized" "strncmp 2" } */
+  return i;
+}
+
+void test_strncpy (char *d)
+{
+  extern char* strncpy (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  strncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" "strncpy" } */
+}
+
+char* test_strndup (size_t n)
+{
+  extern char* strndup (const char*, size_t);
+
+  char a7[7];
+  char *s;
+  s = strndup (a7, n);        /* { dg-warning ".a7. is used uninitialized" "strndup" } */
+  return s;
+}
+
+size_t test_strlen (void)
+{
+  extern size_t strlen (const char*);
+
+  char a7[7];
+  return strlen (a7);         /* { dg-warning ".a7. is used uninitialized" "strlen" } */
+}
diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
index fe8f8f0bc28..b9d455159cb 100644
--- a/gcc/tree-ssa-uninit.c
+++ b/gcc/tree-ssa-uninit.c
@@ -34,6 +34,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "params.h"
 #include "tree-cfg.h"
 #include "cfghooks.h"
+#include "attribs.h"
 
 /* This implements the pass that does predicate aware warning on uses of
    possibly uninitialized variables.  The pass first collects the set of
@@ -218,8 +219,172 @@  check_defs (ao_ref *ref, tree vdef, void *data_)
   return true;
 }
 
+/* Helper of warn_uninitialized_vars.  Determine if Gimple statement
+   STMT reads or may read any uninitialized operand and if so, diagnose
+   the access.  */
+
+static void
+maybe_warn_uninit_access (gimple *stmt, tree arg, ao_ref &ref,
+			  bool always_executed,
+			  unsigned *vdef_cnt, unsigned *oracle_cnt,
+			  unsigned *limit, bool maybe_uninit)
+{
+  /* Do not warn if the base was marked so or this is a
+     hard register var.  */
+  tree base = ao_ref_base (&ref);
+  if ((VAR_P (base)
+       && DECL_HARD_REGISTER (base))
+      || TREE_NO_WARNING (base))
+    return;
+
+  /* Do not warn if the access is fully outside of the
+     variable.  */
+  if (DECL_P (base)
+      && known_ne (ref.size, -1)
+      && known_eq (ref.max_size, ref.size)
+      && (known_le (ref.offset + ref.size, 0)
+	  || (known_ge (ref.offset, 0)
+	      && DECL_SIZE (base)
+	      && TREE_CODE (DECL_SIZE (base)) == INTEGER_CST
+	      && tree_int_cst_le (DECL_SIZE (base),
+				  build_int_cstu (sizetype, ref.offset)))))
+    return;
+
+  /* Limit the walking to a constant number of stmts after
+     we overcommit quadratic behavior for small functions
+     and O(n) behavior.  */
+  if (*oracle_cnt > 128 * 128
+      && *oracle_cnt > *vdef_cnt * 2)
+    *limit = 32;
+  check_defs_data data;
+  bool fentry_reached = false;
+  data.found_may_defs = false;
+
+  int res = walk_aliased_vdefs (&ref, gimple_vuse (stmt),
+				check_defs, &data, NULL,
+				&fentry_reached, *limit);
+  if (res == -1)
+    {
+      *oracle_cnt += *limit;
+      return;
+    }
+  oracle_cnt += res;
+  if (data.found_may_defs)
+    return;
+  /* Do not warn if it can be initialized outside this function.
+     If we did not reach function entry then we found killing
+     clobbers on all paths to entry.  */
+  if (fentry_reached
+      /* ???  We'd like to use ref_may_alias_global_p but that
+	 excludes global readonly memory and thus we get bougs
+	 warnings from p = cond ? "a" : "b" for example.  */
+      && (!VAR_P (base)
+	  || is_global_var (base)))
+    return;
+
+  /* We didn't find any may-defs so on all paths either
+     reached function entry or a killing clobber.  */
+  location_t location
+    = linemap_resolve_location (line_table, gimple_location (stmt),
+				LRK_SPELLING_LOCATION, NULL);
+  if (always_executed)
+    {
+      if (warning_at (location, OPT_Wuninitialized,
+		      "%qE is used uninitialized in this function",
+		      arg))
+	/* ???  This is only effective for decls as in
+	   gcc.dg/uninit-B-O0.c.  Avoid doing this for
+	   maybe-uninit uses as it may hide important
+	   locations.  */
+	TREE_NO_WARNING (arg) = 1;
+    }
+  else if (maybe_uninit)
+    warning_at (location, OPT_Wmaybe_uninitialized,
+		"%qE may be used uninitialized in this function",
+		arg);
+}
+
+/* Helper of warn_uninitialized_vars.  Determine if Gimple statement
+   STMT reads any uninitialized operand.  */
+
+static void
+maybe_warn_uninit_access (gimple *stmt, bool always_executed,
+			  unsigned *vdef_cnt, unsigned *oracle_cnt,
+			  unsigned *limit, bool maybe_uninit)
+{
+  if (gimple_assign_load_p (stmt)
+      && gimple_has_location (stmt))
+    {
+      bool has_bit_insert = false;
+
+      tree lhs = gimple_assign_lhs (stmt);
+
+      /* Do not warn if the access is then used for a BIT_INSERT_EXPR. */
+      if (TREE_CODE (lhs) == SSA_NAME)
+	{
+	  use_operand_p luse_p;
+	  imm_use_iterator liter;
+
+	  FOR_EACH_IMM_USE_FAST (luse_p, liter, lhs)
+	    {
+	      gimple *use_stmt = USE_STMT (luse_p);
+	      /* BIT_INSERT_EXPR first operand should not be considered
+		 a use for the purpose of uninit warnings.  */
+	      if (gassign *ass = dyn_cast <gassign *> (use_stmt))
+		{
+		  if (gimple_assign_rhs_code (ass) == BIT_INSERT_EXPR
+		      && luse_p->use == gimple_assign_rhs1_ptr (ass))
+		    {
+		      has_bit_insert = true;
+		      break;
+		    }
+		}
+	    }
+	}
+
+      if (!has_bit_insert)
+	{
+	  tree arg = gimple_assign_rhs1 (stmt);
+	  if (TREE_NO_WARNING (arg))
+	    return;
+
+	  ao_ref ref;
+	  ao_ref_init (&ref, arg);
+
+	  maybe_warn_uninit_access (stmt, arg, ref, always_executed, vdef_cnt,
+				    oracle_cnt, limit, maybe_uninit);
+	  return;
+	}
+    }
+
+  if (!is_gimple_call (stmt))
+    return;
+
+  /* Iterate over arguments to functions declared with attribute
+     read_only or read_write and diagnose those that are not
+     initialized.  */
+  const attr_access::Kind access_kinds[] = {
+    attr_access::read_only, attr_access::read_write
+  };
+
+  for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i)
+    {
+      attr_access acc = { };
+      acc.kind = access_kinds[i];
+      while (get_attr_access_ptr_and_size (stmt, &acc))
+	{
+	  ao_ref ref;
+	  ao_ref_init_from_ptr_and_size (&ref, acc.ptr, acc.size);
+	  tree arg = ref.base;
+
+	  maybe_warn_uninit_access (stmt, arg, ref, always_executed, vdef_cnt,
+				    oracle_cnt, limit, maybe_uninit);
+	}
+    }
+}
+
 static unsigned int
-warn_uninitialized_vars (bool warn_possibly_uninitialized)
+warn_uninitialized_vars (bool maybe_uninit)
 {
   gimple_stmt_iterator gsi;
   basic_block bb;
@@ -236,7 +401,6 @@  warn_uninitialized_vars (bool warn_possibly_uninitialized)
 	  gimple *stmt = gsi_stmt (gsi);
 	  use_operand_p use_p;
 	  ssa_op_iter op_iter;
-	  tree use;
 
 	  if (is_gimple_debug (stmt))
 	    continue;
@@ -253,13 +417,13 @@  warn_uninitialized_vars (bool warn_possibly_uninitialized)
 		      && use_p->use == gimple_assign_rhs1_ptr (ass))
 		    continue;
 		}
-	      use = USE_FROM_PTR (use_p);
+	      tree use = USE_FROM_PTR (use_p);
 	      if (always_executed)
 		warn_uninit (OPT_Wuninitialized, use, SSA_NAME_VAR (use),
 			     SSA_NAME_VAR (use),
 			     "%qD is used uninitialized in this function", stmt,
 			     UNKNOWN_LOCATION);
-	      else if (warn_possibly_uninitialized)
+	      else if (maybe_uninit)
 		warn_uninit (OPT_Wmaybe_uninitialized, use, SSA_NAME_VAR (use),
 			     SSA_NAME_VAR (use),
 			     "%qD may be used uninitialized in this function",
@@ -271,115 +435,9 @@  warn_uninitialized_vars (bool warn_possibly_uninitialized)
 	  if (gimple_vdef (stmt))
 	    vdef_cnt++;
 
-	  if (gimple_assign_load_p (stmt)
-	      && gimple_has_location (stmt))
-	    {
-	      tree rhs = gimple_assign_rhs1 (stmt);
-	      tree lhs = gimple_assign_lhs (stmt);
-	      bool has_bit_insert = false;
-	      use_operand_p luse_p;
-	      imm_use_iterator liter;
-
-	      if (TREE_NO_WARNING (rhs))
-		continue;
-
-	      ao_ref ref;
-	      ao_ref_init (&ref, rhs);
-
-	      /* Do not warn if the base was marked so or this is a
-	         hard register var.  */
-	      tree base = ao_ref_base (&ref);
-	      if ((VAR_P (base)
-		   && DECL_HARD_REGISTER (base))
-		  || TREE_NO_WARNING (base))
-		continue;
-
-	      /* Do not warn if the access is fully outside of the
-	         variable.  */
-	      poly_int64 decl_size;
-	      if (DECL_P (base)
-		  && known_size_p (ref.size)
-		  && ((known_eq (ref.max_size, ref.size)
-		       && known_le (ref.offset + ref.size, 0))
-		      || (known_ge (ref.offset, 0)
-			  && DECL_SIZE (base)
-			  && poly_int_tree_p (DECL_SIZE (base), &decl_size)
-			  && known_le (decl_size, ref.offset))))
-		continue;
-
-	      /* Do not warn if the access is then used for a BIT_INSERT_EXPR. */
-	      if (TREE_CODE (lhs) == SSA_NAME)
-	        FOR_EACH_IMM_USE_FAST (luse_p, liter, lhs)
-		  {
-		    gimple *use_stmt = USE_STMT (luse_p);
-                    /* BIT_INSERT_EXPR first operand should not be considered
-		       a use for the purpose of uninit warnings.  */
-		    if (gassign *ass = dyn_cast <gassign *> (use_stmt))
-		      {
-			if (gimple_assign_rhs_code (ass) == BIT_INSERT_EXPR
-			    && luse_p->use == gimple_assign_rhs1_ptr (ass))
-			  {
-			    has_bit_insert = true;
-			    break;
-			  }
-		      }
-		  }
-	      if (has_bit_insert)
-		continue;
-
-	      /* Limit the walking to a constant number of stmts after
-	         we overcommit quadratic behavior for small functions
-		 and O(n) behavior.  */
-	      if (oracle_cnt > 128 * 128
-		  && oracle_cnt > vdef_cnt * 2)
-		limit = 32;
-	      check_defs_data data;
-	      bool fentry_reached = false;
-	      data.found_may_defs = false;
-	      use = gimple_vuse (stmt);
-	      int res = walk_aliased_vdefs (&ref, use,
-					    check_defs, &data, NULL,
-					    &fentry_reached, limit);
-	      if (res == -1)
-		{
-		  oracle_cnt += limit;
-		  continue;
-		}
-	      oracle_cnt += res;
-	      if (data.found_may_defs)
-		continue;
-	      /* Do not warn if it can be initialized outside this function.
-	         If we did not reach function entry then we found killing
-		 clobbers on all paths to entry.  */
-	      if (fentry_reached
-		  /* ???  We'd like to use ref_may_alias_global_p but that
-		     excludes global readonly memory and thus we get bougs
-		     warnings from p = cond ? "a" : "b" for example.  */
-		  && (!VAR_P (base)
-		      || is_global_var (base)))
-		continue;
-
-	      /* We didn't find any may-defs so on all paths either
-	         reached function entry or a killing clobber.  */
-	      location_t location
-		= linemap_resolve_location (line_table, gimple_location (stmt),
-					    LRK_SPELLING_LOCATION, NULL);
-	      if (always_executed)
-		{
-		  if (warning_at (location, OPT_Wuninitialized,
-				  "%qE is used uninitialized in this function",
-				  rhs))
-		    /* ???  This is only effective for decls as in
-		       gcc.dg/uninit-B-O0.c.  Avoid doing this for
-		       maybe-uninit uses as it may hide important
-		       locations.  */
-		    TREE_NO_WARNING (rhs) = 1;
-		}
-	      else if (warn_possibly_uninitialized)
-		warning_at (location, OPT_Wmaybe_uninitialized,
-			    "%qE may be used uninitialized in this function",
-			    rhs);
-	    }
+	  maybe_warn_uninit_access (stmt, always_executed,
+				    &vdef_cnt, &oracle_cnt, &limit,
+				    maybe_uninit);
 	}
     }
 
@@ -2666,7 +2724,7 @@  pass_late_warn_uninitialized::execute (function *fun)
   /* Re-do the plain uninitialized variable check, as optimization may have
      straightened control flow.  Do this first so that we don't accidentally
      get a "may be" warning when we'd have seen an "is" warning later.  */
-  warn_uninitialized_vars (/*warn_possibly_uninitialized=*/1);
+  warn_uninitialized_vars (/*maybe_uninit=*/1);
 
   timevar_push (TV_TREE_UNINIT);
 
@@ -2736,7 +2794,7 @@  execute_early_warn_uninitialized (void)
      optimization we need to warn here about "may be uninitialized".  */
   calculate_dominance_info (CDI_POST_DOMINATORS);
 
-  warn_uninitialized_vars (/*warn_possibly_uninitialized=*/!optimize);
+  warn_uninitialized_vars (/*maybe_uninit=*/!optimize);
 
   /* Post-dominator information cannot be reliably updated.  Free it
      after the use.  */