public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Martin Sebor <msebor@gmail.com>
To: Jeff Law <law@redhat.com>, gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
Date: Sun, 30 Jun 2019 21:50:00 -0000	[thread overview]
Message-ID: <e686e5c3-9110-a4dc-fa2a-7a88c98fe4d8@gmail.com> (raw)
In-Reply-To: <0d4c1fb4-f9e7-7127-bf4c-051e0b7f3f5c@redhat.com>

[-- Attachment #1: Type: text/plain, Size: 18201 bytes --]

On 6/26/19 6:11 PM, Jeff Law wrote:
> On 6/18/19 9:19 PM, Martin Sebor wrote:
>> On 6/14/19 2:59 PM, Jeff Law wrote:
> [ big snip ]
>>> A COND_EXPR on the RHS of an assignment is valid gimple.  That's what we
>>> need to consider here -- what is and what is not valid gimple.  And its
>>> more likely that PHIs will be transformed into RHS COND_EXPRs -- that's
>>> standard practice for if-conversion.
>>>
>>> Gosh, how to get one?  It happens all the time :-)  Since I know DOM so
>>> well, I just shoved a quick assert into optimize_stmt to abort if we
>>> encounter a gimple assignment where the RHS is a COND_EXPR.  It blew up
>>> instantly building libgcc :-)
>>>
>>> COmpile the attached code with -O2 -m32.
>>
>> I wasn't saying it's not valid Gimple, just that I hadn't seen it
>> come up here despite compiling Glibc and the kernel with the patched
>> GCC.  The only codes I saw are these:
>>
>>    addr_expr, array_ref, bit_and_expr, component_ref, max_expr,
>>    mem_ref, nop_expr, parm_decl, pointer_plus_expr, ssa_name,
>>    and var_decl
> The only one here that's really surprising is the MAX_EXPR.  But it is
> what it is.
> 
>>
>> What I was asking for is a test case that makes COND_EXPR come up
>> in this context.  But I managed to find one by triggering the ICE
>> with GDB.  The file reduced to the following test case:
> Sorry.  email can be a tough medium to nail down specific details.
> 
>>
>>    extern struct S s;   // S has to be an incomplete type
>>
>>    void* f (int n)
>>    {
>>      void *p;
>>      int x = 0;
>>
>>      for (int i = n; i >= 0; i--)
>>        {
>>          p = &s;
>>          if (p == (void*)-1)
>>             x = 1;
>>          else if (p)
>>             return p;
>>        }
>>
>>      return x ? (void*)-1 : 0;
>>    }
>>
>> and the dump:
>>
>>    <bb 6> [local count: 59055800]:
>>    # x_10 = PHI <1(5), 0(2)>
>>    _5 = x_10 != 0 ? -1B : 0B;
>>
>>    <bb 7> [local count: 114863532]:
>>    # _3 = PHI <&s(4), _5(6), &s(3)>
>>    return _3;
>>
>> It seems a little odd that the COND_EXPR disappears when either
>> of the constant addresses becomes the address of an object (or
>> the result of alloca), and also when the number of iterations
>> of the loop is constant.  That's probably why it so rarely comes
>> up in this context.
> Going into phiopt2 we have:
> 
> ;;   basic block 6, loop depth 0
> ;;    pred:       5
>    if (x_1 != 0)
>      goto <bb 8>; [71.00%]
>    else
>      goto <bb 7>; [29.00%]
> ;;    succ:       8
> ;;                7
> 
> ;;   basic block 7, loop depth 0
> ;;    pred:       6
> ;;    succ:       8
> 
> ;;   basic block 8, loop depth 0
> ;;    pred:       3
> ;;                7
> ;;                6
>    # _3 = PHI <&s(3), 0B(7), -1B(6)>
>    return _3;
> 
> The subgraph starting at block #6 is a classic case for turning branchy
> code into straightline code using a COND_EXPR on the RHS of an
> assignment.  So you end up with something like this:
> 
> ;;   basic block 6, loop depth 0
> ;;    pred:       5
>    _5 = x_1 != 0 ? -1B : 0B;
> ;;    succ:       7
> 
> ;;   basic block 7, loop depth 0
> ;;    pred:       3
> ;;                6
>    # _3 = PHI <&s(3), _5(6)>
>    return _3;
> 
> 
> Now for this specific case within phiopt we are limited to cases there
> the result is 0/1 or 0/-1.  That's why you get something different when
> you exchange one of the constants for the address of an object, or
> anything else for that matter.
> 
> This is all a bit academic -- the key point is that we can have a
> COND_EXPR on the RHS of an assignment.  That's allowed by gimple.
> 
> Sadly this is also likely one of the places where target characteristics
> come into play -- targets define a BRANCH_COST which can significantly
> change the decisions for the initial generation of conditionals.  It's
> one of the things that makes writing  tests for jump threading, if
> conversion and other optimizations so damn painful -- on one target
> we'll have a series of conditional jumps, on anothers we may have a
> series of logicals, potentially with COND_EXPRs.
> 
> 
>>
>> That said, even though I've seen a few references to COND_EXPR
>> in the midle-end I have been assuming that they, in general, do
>> get transformed into PHIs.  But checking the internals manual
>> I found in Section 12.6.3 this:
>>
>>    A C ?: expression is converted into an if statement with each
>>    branch assigning to the same temporary. ... The GIMPLE level
>>    if-conversion pass re-introduces ?: expression, if appropriate.
>>    It is used to vectorize loops with conditions using vector
>>    conditional operations.
>>
>> This GDB test case is likely the result of this reintroduction.
> Nope.  It happens much earlier in the pipeline :-)
> 
> 
>>>
>>> And in a more general sense, this kind of permissiveness is not future
>>> proof.  What happens if someone needs to add another EXPR node that is
>>> valid on the RHS where such recursion is undesirable?  How are they
>>> supposed to know that we've got this permissive recursive call and that
>>> it's going to do the wrong thing?  And if it's an EXPR node with no
>>> arguments, then we're going to do a read out of the bounds of the object
>>> and all bets are off at that point (yes we have zero operand EXPR nodes,
>>> but thankfully I don't think they should up in the contexts you care about).
>>>
>>
>> Sure.  When things are hardcoded this way the same argument can
>> be made both ways.  If we just handle A and B and then someone
>> adds an X that matches one of the two, it won't be handled. Either
>> way, some work is necessary to deal with the new Z.  One way to
>> avoid it would be by providing an API to query this property of
>> a code (e.g., a function like int gimple_result_aliases_operand
>> (gimple *stmt, int opno)).
> Adding a new opcode could have caused the original code to generate
> incorrect code.  In the new version an indirect opcode would just result
> in something we refuse to analyze -- so we could miss a warning, but
> more importantly we would _not_ transform the return statement so we'd
> be safe WRT correct code generation.
> 
> WRT making an API to do these kinds of queries.  Sure.  It's a fairly
> natural extension to stuff we've done in the past.  You could (for
> example) wrap up all kinds existing properties of nodes into an API.
> You'll see that's already done in a fairly ad-hoc fashion, but it's not
> even close to being pervasive or standard practice.
> 
> I would certainly applaud bringing some sanity to this kind of issue and
> iterating towards a better place.  But it's ugly, painful work with
> little visible end user benefit.
> 
>>
>>> But those seem like distinct issues.  The one I pointed out looks like a
>>> pretty simple case to handle.  It's just a logic error.
>>
>> It wasn't a logic error.  I simply didn't think it was necessary
>> to worry about the accuracy of an inherently inaccurate warning,
>> and I didn't want make more intrusive changes than was called for
>> by my simple fix/enhancement.  I was also keeping in mind your
>> preference for smaller change sets.  But since you insist I went
>> ahead and improved things.  The warning is all the better for it,
>> and the changes I made exposed a number of other bugs in GCC.
>> At the same time, it seems that the bar for a simple bug fixes
>> and small enhancements should not be in excess of what's possible
>> by the original design.
> It looked like a logic error to me that could be easily fixed.  I didn't
> want to make it out to a huge deal.  ANd yes, there's a natural tension
> between addressing small stuff as you see them in a larger kit and
> breaking things down and iterating.  There aren't hard and fast rules here.
> 
> 
> 
>>
>>
>> gcc-71924.diff
>>
>> PR middle-end/71924 - missing -Wreturn-local-addr returning alloca result
>> PR middle-end/90549 - missing -Wreturn-local-addr maybe returning an address of a local array plus offset
>>
>> gcc/ChangeLog:
>>
>> 	PR middle-end/71924
>> 	PR middle-end/90549
>> 	* gimple-ssa-isolate-paths.c (isolate_path): Add attribute.
>> 	(args_loc_t): New type.
>> 	(args_loc_t, locmap_t): same.
>> 	(diag_returned_locals): New function.
>> 	(is_addr_local): Same.
>> 	(handle_return_addr_local_phi_arg, warn_return_addr_local): Same.
>> 	(find_implicit_erroneous_behavior): Call warn_return_addr_local_phi_arg.
>> 	(find_explicit_erroneous_behavior): Call warn_return_addr_local.
>>
>> libgcc/ChangeLog:
>> 	* generic-morestack.c: Disable -fisolate-erroneous-paths-dereference
>> 	to get around PR libgcc/90918.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR middle-end/71924
>> 	PR middle-end/90549
>> 	* gcc.dg/Wreturn-local-addr-2.c: New test.
>> 	* gcc.dg/Wreturn-local-addr-4.c: New test.
>> 	* gcc.dg/Wreturn-local-addr-5.c: New test.
>> 	* gcc.dg/Wreturn-local-addr-6.c: New test.
>> 	* gcc.dg/Wreturn-local-addr-7.c: New test.
>> 	* gcc.dg/Wreturn-local-addr-8.c: New test.
>> 	* gcc.dg/Walloca-4.c: Prune expected warnings.
>> 	* gcc.dg/pr41551.c: Same.
>> 	* gcc.dg/pr59523.c: Same.
>> 	* gcc.dg/tree-ssa/pr88775-2.c: Same.
>> 	* gcc.dg/winline-7.c: Same.
>>
>> diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
>> index 33fe352bb23..13b1f5f2349 100644
>> --- a/gcc/gimple-ssa-isolate-paths.c
>> +++ b/gcc/gimple-ssa-isolate-paths.c
>> @@ -130,7 +130,7 @@ insert_trap (gimple_stmt_iterator *si_p, tree op)
>>   
>>      Return BB'.  */
>>   
>> -basic_block
>> +ATTRIBUTE_RETURNS_NONNULL basic_block
>>   isolate_path (basic_block bb, basic_block duplicate,
>>   	      edge e, gimple *stmt, tree op, bool ret_zero)
>>   {
>> @@ -341,6 +341,283 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
>>     return false;
>>   }
>>   
>> +/* Describes the property of a return statement that may return
>> +   the address of one or more local variables.  */
>> +struct args_loc_t
>> +{
>> +  args_loc_t (): nargs (), locvec (), ptr (&ptr)
>> +  {
>> +    locvec.create (4);
>> +  }
>> +
>> +  args_loc_t (const args_loc_t &rhs)
>> +    : nargs (rhs.nargs), locvec (rhs.locvec.copy ()), ptr (&ptr) { }
>> +
>> +  args_loc_t& operator= (const args_loc_t &rhs)
>> +  {
>> +    nargs = rhs.nargs;
>> +    locvec.release ();
>> +    locvec = rhs.locvec.copy ();
>> +    return *this;
>> +  }
>> +
>> +  ~args_loc_t ()
>> +  {
>> +    locvec.release ();
>> +    gcc_assert (ptr == &ptr);
>> +  }
>> +
>> +  /* For a PHI in a return statement its number of arguments.  When less
>> +     than LOCVEC.LENGTH () implies that an address of local may but need
>> +     not be returned by the statement.  Otherwise it implies it definitely
>> +     is returned.  Used to issue "may be" vs "is" diagnostics.  */
>> +  unsigned nargs;
>> +  /* The locations of local variables/alloca calls returned by the return
>> +     statement.  Avoid using auto_vec here since it's not safe to copy due
>> +     to pr90904.  */
>> +  vec <location_t> locvec;
>> +  void *ptr;
>> +};
> Make this a class, please.  We use "struct" for POD.

I've made this change.

> Is there a strong need for an overloaded operator?  Our guidelines
> generally discourage operator overloads.  This one seems on the border
> to me.  Others may have different ideas where the line is.  If we really
> don't need the overloaded operator, then we can avoid the controversy
> completely.

The copy assignment operator is necessary for the type to be stored
in a hash_map.  Otherwise, the implicitly generated assignment will
be used instead which is unsafe in vec and results in memory
corription (I opened bug 90904 for this).  After working around this
bug by providing the assignment operator I then ran into the hash_map
bug 90959 that also results in memory corruption.  I spent a few fun
hours tracking down the GCC miscompilations that they led to.

The golden rule in C++ is that every type should either be safe to
copy and assign with the expected semantics or made not assignable
and not copyable by declaring the copy ctor and copy assignment
operator private (or deleted in more modern C++).  It would be very
helpful to detect this kind of problem (classes that aren't safely
assignable and copyable because they acquire/release resources in
ctors/dtors).  Not just to us in GCC but I'm sure to others as well.
It's something I've been hoping to implement for a while now.

>> +
>> +/* Return true if EXPR is a expression of pointer type that refers
>> +   to the address of a variable with automatic storage duration.
>> +   If so, add an entry to *PLOCMAP and insert INTO PLOCMAP->LOCVEC
>> +   the locations of the corresponding local variables whose address
>> +   is returned by the statement.  VISITED is a bitmap of PHI nodes
>> +   already visited by recursive calls.  */
>> +
>> +static bool
>> +is_addr_local (gimple *return_stmt, tree exp, locmap_t *plocmap,
>> +	       hash_set<gphi *> *visited)
>> +{
>> +  if (TREE_CODE (exp) == ADDR_EXPR)
>> +    {
>> +      tree baseaddr = get_base_address (TREE_OPERAND (exp, 0));
>> +      if (TREE_CODE (baseaddr) == MEM_REF)
>> +	return is_addr_local (return_stmt, TREE_OPERAND (baseaddr, 0),
>> +			      plocmap, visited);
>> +
>> +      if ((!VAR_P (baseaddr)
>> +	   || is_global_var (baseaddr))
>> +	  && TREE_CODE (baseaddr) != PARM_DECL)
>> +	return false;
>> +
>> +      args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
>> +      argsloc.locvec.safe_push (DECL_SOURCE_LOCATION (baseaddr));
>> +      return true;
>> +    }
>> +
>> +  if (!POINTER_TYPE_P (TREE_TYPE (exp)))
>> +    return false;
>> +
>> +  if (TREE_CODE (exp) == SSA_NAME)
>> +    {
>> +      gimple *def_stmt = SSA_NAME_DEF_STMT (exp);
>> +      enum gimple_code code = gimple_code (def_stmt);
>> +
>> +      if (is_gimple_assign (def_stmt))
>> +	{
>> +	  tree type = TREE_TYPE (gimple_assign_lhs (def_stmt));
>> +	  if (POINTER_TYPE_P (type))
>> +	    {
>> +	      tree_code code = gimple_assign_rhs_code (def_stmt);
>> +	      tree ptr1 = NULL_TREE, ptr2 = NULL_TREE;
>> +	      if (code == COND_EXPR)
>> +		{
>> +		  ptr1 = gimple_assign_rhs2 (def_stmt);
>> +		  ptr2 = gimple_assign_rhs3 (def_stmt);
>> +		}
>> +	      else if (code == MAX_EXPR || code == MIN_EXPR)
>> +		{
>> +		  ptr1 = gimple_assign_rhs1 (def_stmt);
>> +		  ptr2 = gimple_assign_rhs2 (def_stmt);
>> +		}
>> +	      else if (code == ADDR_EXPR
>> +		       || code == NOP_EXPR
>> +		       || code == POINTER_PLUS_EXPR)
>> +		ptr1 = gimple_assign_rhs1 (def_stmt);
>> +
>> +	      /* Avoid short-circuiting the logical OR result in case
>> +		 both oerands refer to local variables, in which case
>> +		 both should be identified in the warning.  */
>> +	      bool res1 = false, res2 = false;
>> +	      if (ptr1)
>> +		res1 = is_addr_local (return_stmt, ptr1, plocmap, visited);
>> +	      if (ptr2)
>> +		res2 = is_addr_local (return_stmt, ptr2, plocmap, visited);
>> +	      return res1 || res2;
>> +	    }
>> +	  return false;
>> +	}
> s/oerands/operands/  a few lines up.
> 
> So how do you deal with the case where one operand of a
> {MIN,MAX,COND}_EXPR is the address of a local, but the other is not?  It
> seems like we'll end up returning true from this function in that case
> and potentially trigger changing the return value to NULL.  Or am I
> missing something?

I only briefly considered MIN/MAX but because in C and C++ the less
than and greater than expressions are only defined for pointers into
the same array/object or subobject and I didn't ponder it too hard.
(In C they're undefined otherwise, in C++ the result is unspecified.)

But you bring it up because you think the address should be returned
regardless, even if the expression is invalid (or if it's COND_EXPR
with mixed local/nonlocal addresses) so I've changed it to do that
for all these explicitly handled codes and added tests to verify it.

Retested on x86_64-linux.

Martin


[-- Attachment #2: gcc-71924.diff --]
[-- Type: text/x-patch, Size: 64539 bytes --]

PR middle-end/71924 - missing -Wreturn-local-addr returning alloca result
PR middle-end/90549 - missing -Wreturn-local-addr maybe returning an address of a local array plus offset

gcc/ChangeLog:

	PR middle-end/71924
	PR middle-end/90549
	* gimple-ssa-isolate-paths.c (isolate_path): Add attribute.  Update
	comment.
	(args_loc_t): New type.
	(args_loc_t, locmap_t): same.
	(diag_returned_locals): New function.
	(is_addr_local): Same.
	(handle_return_addr_local_phi_arg, warn_return_addr_local): Same.
	(find_implicit_erroneous_behavior): Call warn_return_addr_local_phi_arg.
	(find_explicit_erroneous_behavior): Call warn_return_addr_local.

libgcc/ChangeLog:
	* generic-morestack.c: Disable -fisolate-erroneous-paths-dereference
	to get around PR libgcc/90918.

gcc/testsuite/ChangeLog:

	PR middle-end/71924
	PR middle-end/90549
	* gcc.c-torture/execute/return-addr.c: New test.
	* gcc.dg/Wreturn-local-addr-2.c: New test.
	* gcc.dg/Wreturn-local-addr-4.c: New test.
	* gcc.dg/Wreturn-local-addr-5.c: New test.
	* gcc.dg/Wreturn-local-addr-6.c: New test.
	* gcc.dg/Wreturn-local-addr-7.c: New test.
	* gcc.dg/Wreturn-local-addr-8.c: New test.
	* gcc.dg/Wreturn-local-addr-9.c: New test.
	* gcc.dg/Walloca-4.c: Handle expected warnings.
	* gcc.dg/pr41551.c: Same.
	* gcc.dg/pr59523.c: Same.
	* gcc.dg/tree-ssa/pr88775-2.c: Same.
	* gcc.dg/tree-ssa/alias-37.c: Same.
	* gcc.dg/winline-7.c: Same.

diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
index 33fe352bb23..764c1c00f0e 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -128,9 +128,9 @@ insert_trap (gimple_stmt_iterator *si_p, tree op)
 
    DUPLICATE is a pre-existing duplicate, use it as BB' if it exists.
 
-   Return BB'.  */
+   Return BB' (which may be equal to DUPLICATE).  */
 
-basic_block
+ATTRIBUTE_RETURNS_NONNULL basic_block
 isolate_path (basic_block bb, basic_block duplicate,
 	      edge e, gimple *stmt, tree op, bool ret_zero)
 {
@@ -341,6 +341,322 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
   return false;
 }
 
+/* Describes the property of a return statement that may return
+   the address of one or more local variables.  The type must
+   be safely assignable and copyable so that it can be stored in
+   a hash_map.  */
+class args_loc_t
+{
+ public:
+
+  args_loc_t (): nargs (), locvec (), ptr (&ptr)
+  {
+    locvec.create (4);
+  }
+
+  args_loc_t (const args_loc_t &rhs)
+    : nargs (rhs.nargs), locvec (rhs.locvec.copy ()), ptr (&ptr) { }
+
+  args_loc_t& operator= (const args_loc_t &rhs)
+  {
+    nargs = rhs.nargs;
+    locvec.release ();
+    locvec = rhs.locvec.copy ();
+    return *this;
+  }
+
+  ~args_loc_t ()
+  {
+    locvec.release ();
+    gcc_assert (ptr == &ptr);
+  }
+
+  /* For a PHI in a return statement its number of arguments.  When greater
+     than LOCVEC.LENGTH () implies that an address of one of the locals in
+     LOCVEC may but need not be returned by the statement.  Otherwise,
+     unless both are zero, it implies it definitely is returned.  */
+  unsigned nargs;
+  /* The locations of local variables/alloca calls returned by the return
+     statement.  Avoid using auto_vec here since it's not safe to copy due
+     to pr90904.  */
+  vec <location_t> locvec;
+  void *ptr;
+};
+
+/* A mapping from a return statement to the locations of local variables
+   whose addresses it may return.  */
+typedef hash_map <gimple *, args_loc_t> locmap_t;
+
+/* Given the LOCMAP mapping, issue diagnostics about returning addresses
+   of local variables.  When MAYBE is set, all diagnostics will be of
+   the "may return" kind.  Otherwise each will be determined based on
+   the equality of the corresponding NARGS and LOCVEC.LENGTH () values.  */
+
+static void
+diag_returned_locals (bool maybe, const locmap_t &locmap)
+{
+  for (locmap_t::iterator it = locmap.begin (); it != locmap.end (); ++it)
+    {
+      gimple *stmt = (*it).first;
+      const args_loc_t &argsloc = (*it).second;
+      location_t stmtloc = gimple_location (stmt);
+
+      auto_diagnostic_group d;
+      unsigned nargs = argsloc.locvec.length ();
+      if (warning_at (stmtloc, OPT_Wreturn_local_addr,
+		      (maybe || argsloc.nargs > nargs
+		       ? G_("function may return address of local variable")
+		       : G_("function returns address of local variable"))))
+	{
+	  for (unsigned i = 0; i != nargs; ++i)
+	    inform (argsloc.locvec[i], "declared here");
+	}
+    }
+}
+
+/* Return true if EXPR is an expression of pointer type that refers
+   to the address of one or more variables with automatic storage
+   duration.  If so, add an entry to *PLOCMAP and insert into
+   PLOCMAP->LOCVEC the locations of the corresponding local variables
+   whose address is returned by the RETURN_STMT (which may be set to
+   (gimple*)-1 as a placeholder for such a statement).  VISITED is
+   a bitmap of PHI nodes already visited by recursive calls.  When
+   null, PHI expressions are not considered.  */
+
+static bool
+is_addr_local (gimple *return_stmt, tree exp, locmap_t *plocmap,
+	       hash_set<gphi *> *visited)
+{
+  if (TREE_CODE (exp) == ADDR_EXPR)
+    {
+      tree baseaddr = get_base_address (TREE_OPERAND (exp, 0));
+      if (TREE_CODE (baseaddr) == MEM_REF)
+	return is_addr_local (return_stmt, TREE_OPERAND (baseaddr, 0),
+			      plocmap, visited);
+
+      if ((!VAR_P (baseaddr)
+	   || is_global_var (baseaddr))
+	  && TREE_CODE (baseaddr) != PARM_DECL)
+	return false;
+
+      args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+      argsloc.locvec.safe_push (DECL_SOURCE_LOCATION (baseaddr));
+      return true;
+    }
+
+  if (!POINTER_TYPE_P (TREE_TYPE (exp)))
+    return false;
+
+  if (TREE_CODE (exp) == SSA_NAME)
+    {
+      gimple *def_stmt = SSA_NAME_DEF_STMT (exp);
+      enum gimple_code code = gimple_code (def_stmt);
+
+      if (is_gimple_assign (def_stmt))
+	{
+	  tree type = TREE_TYPE (gimple_assign_lhs (def_stmt));
+	  if (POINTER_TYPE_P (type))
+	    {
+	      tree_code code = gimple_assign_rhs_code (def_stmt);
+	      tree ptr1 = NULL_TREE, ptr2 = NULL_TREE;
+
+	      /* Set to the number of arguments examined that should
+		 be added to ARGSLOC->NARGS to identify expressions
+		 only some but not all of whose operands refer to local
+		 addresses.  */
+	      unsigned nargs = 0;
+	      if (code == COND_EXPR)
+		{
+		  ptr1 = gimple_assign_rhs2 (def_stmt);
+		  ptr2 = gimple_assign_rhs3 (def_stmt);
+		  nargs = 2;
+		}
+	      else if (code == MAX_EXPR || code == MIN_EXPR)
+		{
+		  ptr1 = gimple_assign_rhs1 (def_stmt);
+		  ptr2 = gimple_assign_rhs2 (def_stmt);
+		  nargs = 2;
+		}
+	      else if (code == ADDR_EXPR
+		       || code == NOP_EXPR
+		       || code == POINTER_PLUS_EXPR)
+		/* Leave NARGS at zero and let the recursive call set it.  */
+		ptr1 = gimple_assign_rhs1 (def_stmt);
+
+	      /* Avoid short-circuiting the logical OR result in case
+		 both operands refer to local variables, in which case
+		 both should be considered and identified in the warning.  */
+	      bool res1 = false, res2 = false;
+	      if (ptr1)
+		res1 = is_addr_local (return_stmt, ptr1, plocmap, visited);
+	      if (ptr2)
+		res2 = is_addr_local (return_stmt, ptr2, plocmap, visited);
+
+	      if (nargs)
+		if (args_loc_t *argsloc = plocmap->get (return_stmt))
+		  argsloc->nargs += nargs;
+
+	      return res1 || res2;
+	    }
+	  return false;
+	}
+
+      if (code == GIMPLE_CALL
+	  && gimple_call_builtin_p (def_stmt))
+	{
+	  /* Handle alloca and friends that return pointers to automatic
+	     storage.  */
+	  tree fn = gimple_call_fndecl (def_stmt);
+	  int code = DECL_FUNCTION_CODE (fn);
+	  if (code == BUILT_IN_ALLOCA
+	      || code == BUILT_IN_ALLOCA_WITH_ALIGN
+	      || code == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX)
+	    {
+	      args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+	      argsloc.locvec.safe_push (gimple_location (def_stmt));
+	      return true;
+	    }
+
+	  if (gimple_call_num_args (def_stmt) < 1)
+	    return false;
+
+	  /* Recursively examine the first argument of calls to built-ins
+	     that return it.  */
+	  switch (code)
+	    {
+	    case BUILT_IN_MEMCPY:
+	    case BUILT_IN_MEMCPY_CHK:
+	    case BUILT_IN_MEMPCPY:
+	    case BUILT_IN_MEMPCPY_CHK:
+	    case BUILT_IN_MEMMOVE:
+	    case BUILT_IN_MEMMOVE_CHK:
+	    case BUILT_IN_STPCPY:
+	    case BUILT_IN_STPCPY_CHK:
+	    case BUILT_IN_STPNCPY:
+	    case BUILT_IN_STPNCPY_CHK:
+	    case BUILT_IN_STRCAT:
+	    case BUILT_IN_STRCAT_CHK:
+	    case BUILT_IN_STRCHR:
+	    case BUILT_IN_STRCPY:
+	    case BUILT_IN_STRCPY_CHK:
+	    case BUILT_IN_STRNCAT:
+	    case BUILT_IN_STRNCAT_CHK:
+	    case BUILT_IN_STRNCPY:
+	    case BUILT_IN_STRNCPY_CHK:
+	    case BUILT_IN_STRRCHR:
+	    case BUILT_IN_STRSTR:
+	      return is_addr_local (return_stmt,
+				    gimple_call_arg (def_stmt, 0),
+				    plocmap, visited);
+	    default:
+	      return false;
+	    }
+	}
+
+      if (code == GIMPLE_PHI && visited)
+	{
+	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
+	  if (visited->add (phi_stmt))
+	    return false;
+
+	  unsigned count = 0;
+	  unsigned nargs = gimple_phi_num_args (phi_stmt);
+	  args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+	  /* Bump up the number of operands examined by the number of
+	     operands of this PHI.  */
+	  argsloc.nargs += nargs;
+	  for (unsigned i = 0; i < gimple_phi_num_args (phi_stmt); ++i)
+	    {
+	      tree arg = gimple_phi_arg_def (phi_stmt, i);
+	      if (is_addr_local (return_stmt, arg, plocmap, visited))
+		++count;
+	    }
+	  return count != 0;
+	}
+    }
+
+  return false;
+}
+
+/* Detect returning the address of a local variable in a PHI result LHS
+   and argument ARG and PHI edge E in basic block BB.  Add an entry for
+   each use to LOCMAP, setting its NARGS member to the NARGS argument
+   (the number of PHI operands) plus the number of arguments in binary
+   expressions refereced by ARG.  Call isolate_path for each returned
+   address and set *ISOLATED to true if called.
+   Return either DUPLICATE or the most recent result of isolate_path.  */
+
+static basic_block
+handle_return_addr_local_phi_arg (basic_block bb, basic_block duplicate,
+				  tree lhs, tree arg, edge e, locmap_t &locmap,
+				  unsigned nargs, bool *isolated)
+{
+  /* Use (gimple*)-1 as a temporary placeholder and replace it with
+     the return statement below once it is known.  Using a null doesn't
+     work because it's used by the hash_map to mean "no-entry."  Pass
+     null instead of a visited_phis bitmap to avoid descending into
+     PHIs since they are being processed by the caller.  Those that
+     remain will be checked again later.  */
+  if (!is_addr_local ((gimple*)-1, arg, &locmap, NULL))
+    {
+      /* Remove the placeholder regardless of success or failure.  */
+      locmap.remove ((gimple*)-1);
+      return duplicate;
+    }
+
+  const args_loc_t* const placeargsloc = locmap.get ((gimple*)-1);
+  const unsigned nlocs = placeargsloc->locvec.length ();
+  gcc_assert (nlocs);
+
+  /* Add to the number of PHI arguments determined by the caller
+     the number of operands of the expressions referenced by ARG.
+     This lets the caller determine whether it's dealing with
+     a "may return" or "definitely returns."  */
+  nargs += placeargsloc->nargs;
+
+  /* Set to true if any expressions referenced by ARG involve
+     multiple addresses only some of which are those of locals.  */
+  bool maybe = placeargsloc->nargs > placeargsloc->locvec.length ();
+
+  gimple *use_stmt;
+  imm_use_iterator iter;
+
+  /* Look for uses of the PHI result LHS in return statements.  */
+  FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
+    {
+      greturn *return_stmt = dyn_cast <greturn *> (use_stmt);
+      if (!return_stmt)
+	continue;
+
+      if (gimple_return_retval (return_stmt) != lhs)
+	continue;
+
+      /* Add an entry for the return statement and the locations
+	 oof the PHI arguments obtained above to the map.  */
+      args_loc_t &argsloc = locmap.get_or_insert (use_stmt);
+      argsloc.nargs = nargs;
+      unsigned nelts = argsloc.locvec.length () + nlocs;
+      argsloc.locvec.reserve (nelts);
+      argsloc.locvec.splice (placeargsloc->locvec);
+
+      if (!maybe
+	  && (flag_isolate_erroneous_paths_dereference
+	      || flag_isolate_erroneous_paths_attribute)
+	  && gimple_bb (use_stmt) == bb)
+	{
+	  duplicate = isolate_path (bb, duplicate, e,
+				    use_stmt, lhs, true);
+
+	  /* Let caller know the path has been isolated.  */
+	  *isolated = true;
+	}
+    }
+
+  locmap.remove ((gimple*)-1);
+
+  return duplicate;
+}
+
 /* Look for PHI nodes which feed statements in the same block where
    the value of the PHI node implies the statement is erroneous.
 
@@ -352,6 +668,8 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
 static void
 find_implicit_erroneous_behavior (void)
 {
+  locmap_t locmap;
+
   basic_block bb;
 
   FOR_EACH_BB_FN (bb, cfun)
@@ -388,70 +706,46 @@ find_implicit_erroneous_behavior (void)
 	  gphi *phi = si.phi ();
 	  tree lhs = gimple_phi_result (phi);
 
+	  /* Initial number of PHI arguments.  The result may change
+	     from one iteration of the loop below to the next in
+	     response to changes to the CFG but only the initial
+	     value is stored below for use by diagnostics. */
+	  unsigned nargs = gimple_phi_num_args (phi);
+
 	  /* PHI produces a pointer result.  See if any of the PHI's
 	     arguments are NULL.
 
 	     When we remove an edge, we want to reprocess the current
-	     index, hence the ugly way we update I for each iteration.  */
+	     index since the argument at that index will have been
+	     removed, hence the ugly way we update I for each iteration.  */
 	  basic_block duplicate = NULL;
 	  for (unsigned i = 0, next_i = 0;
-	       i < gimple_phi_num_args (phi);
-	       i = next_i)
+	       i < gimple_phi_num_args (phi); i = next_i)
 	    {
-	      tree op = gimple_phi_arg_def (phi, i);
+	      tree arg = gimple_phi_arg_def (phi, i);
 	      edge e = gimple_phi_arg_edge (phi, i);
-	      imm_use_iterator iter;
-	      gimple *use_stmt;
 
+	      /* Advance the argument index unless a path involving
+		 the current argument has been isolated.  */
 	      next_i = i + 1;
-
-	      if (TREE_CODE (op) == ADDR_EXPR)
+	      bool isolated = false;
+	      duplicate = handle_return_addr_local_phi_arg (bb, duplicate, lhs,
+							    arg, e, locmap,
+							    nargs, &isolated);
+	      if (isolated)
 		{
-		  tree valbase = get_base_address (TREE_OPERAND (op, 0));
-		  if ((VAR_P (valbase) && !is_global_var (valbase))
-		      || TREE_CODE (valbase) == PARM_DECL)
-		    {
-		      FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
-			{
-			  greturn *return_stmt
-			    = dyn_cast <greturn *> (use_stmt);
-			  if (!return_stmt)
-			    continue;
-
-			  if (gimple_return_retval (return_stmt) != lhs)
-			    continue;
-
-			  {
-			    auto_diagnostic_group d;
-			    if (warning_at (gimple_location (use_stmt),
-					      OPT_Wreturn_local_addr,
-					      "function may return address "
-					      "of local variable"))
-			      inform (DECL_SOURCE_LOCATION(valbase),
-					"declared here");
-			  }
-
-			  if ((flag_isolate_erroneous_paths_dereference
-			       || flag_isolate_erroneous_paths_attribute)
-			      && gimple_bb (use_stmt) == bb)
-			    {
-			      duplicate = isolate_path (bb, duplicate, e,
-							use_stmt, lhs, true);
-
-			      /* When we remove an incoming edge, we need to
-				 reprocess the Ith element.  */
-			      next_i = i;
-			      cfg_altered = true;
-			    }
-			}
-		    }
+		  cfg_altered = true;
+		  next_i = i;
 		}
 
-	      if (!integer_zerop (op))
+	      if (!integer_zerop (arg))
 		continue;
 
 	      location_t phi_arg_loc = gimple_phi_arg_location (phi, i);
 
+	      imm_use_iterator iter;
+	      gimple *use_stmt;
+
 	      /* We've got a NULL PHI argument.  Now see if the
  	         PHI's result is dereferenced within BB.  */
 	      FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
@@ -480,6 +774,57 @@ find_implicit_erroneous_behavior (void)
 	    }
 	}
     }
+
+  diag_returned_locals (false, locmap);
+}
+
+/* Detect and diagnose returning the address of a local variable
+   in RETURN_STMT in basic block BB.  This only becomes undefined
+   behavior if the result is used, so we do not insert a trap and
+   only return NULL instead.  */
+
+static void
+warn_return_addr_local (basic_block bb, greturn *return_stmt)
+{
+  tree val = gimple_return_retval (return_stmt);
+  if (!val)
+    return;
+
+  locmap_t locmap;
+  hash_set<gphi *> visited_phis;
+  if (!is_addr_local (return_stmt, val, &locmap, &visited_phis))
+    return;
+
+  /* We only need it for this particular case.  */
+  calculate_dominance_info (CDI_POST_DOMINATORS);
+
+  const args_loc_t *argsloc = locmap.get (return_stmt);
+  gcc_assert (argsloc);
+
+  bool maybe = argsloc->nargs > argsloc->locvec.length ();
+  if (!maybe)
+    maybe = !dominated_by_p (CDI_POST_DOMINATORS,
+			     single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)), bb);
+
+  diag_returned_locals (maybe, locmap);
+
+  /* Bail if the statement isn't certain to return the address
+     of a local (e.g., if it involves a conditional expression
+     that wasn't trasnformed into a PHI or if it involves
+     a MAX_EXPR or MIN_EXPR only one of whose operands is a local
+     (even though such an expression isn't valid in C or has
+     defined semantics in C++).  */
+  if (maybe)
+    return;
+
+  /* Do not modify code if the user only asked for warnings.  */
+  if (flag_isolate_erroneous_paths_dereference
+      || flag_isolate_erroneous_paths_attribute)
+    {
+      tree zero = build_zero_cst (TREE_TYPE (val));
+      gimple_return_set_retval (return_stmt, zero);
+      update_stmt (return_stmt);
+    }
 }
 
 /* Look for statements which exhibit erroneous behavior.  For example
@@ -525,49 +870,10 @@ find_explicit_erroneous_behavior (void)
 	      break;
 	    }
 
-	  /* Detect returning the address of a local variable.  This only
-	     becomes undefined behavior if the result is used, so we do not
-	     insert a trap and only return NULL instead.  */
+	  /* Look for a return statement that returns the address
+	     of a local variable or the result of alloca.  */
 	  if (greturn *return_stmt = dyn_cast <greturn *> (stmt))
-	    {
-	      tree val = gimple_return_retval (return_stmt);
-	      if (val && TREE_CODE (val) == ADDR_EXPR)
-		{
-		  tree valbase = get_base_address (TREE_OPERAND (val, 0));
-		  if ((VAR_P (valbase) && !is_global_var (valbase))
-		      || TREE_CODE (valbase) == PARM_DECL)
-		    {
-		      /* We only need it for this particular case.  */
-		      calculate_dominance_info (CDI_POST_DOMINATORS);
-		      const char* msg;
-		      bool always_executed = dominated_by_p
-			(CDI_POST_DOMINATORS,
-			 single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)), bb);
-		      if (always_executed)
-			msg = N_("function returns address of local variable");
-		      else
-			msg = N_("function may return address of "
-				 "local variable");
-		      {
-			auto_diagnostic_group d;
-			if (warning_at (gimple_location (stmt),
-					  OPT_Wreturn_local_addr, msg))
-			  inform (DECL_SOURCE_LOCATION(valbase),
-				  "declared here");
-		      }
-
-		      /* Do not modify code if the user only asked for
-			 warnings.  */
-		      if (flag_isolate_erroneous_paths_dereference
-			  || flag_isolate_erroneous_paths_attribute)
-			{
-			  tree zero = build_zero_cst (TREE_TYPE (val));
-			  gimple_return_set_retval (return_stmt, zero);
-			  update_stmt (stmt);
-			}
-		    }
-		}
-	    }
+	    warn_return_addr_local (bb, return_stmt);
 	}
     }
 }
diff --git a/gcc/hash-map.h b/gcc/hash-map.h
index 588dfda04fa..71cc1dead1d 100644
--- a/gcc/hash-map.h
+++ b/gcc/hash-map.h
@@ -21,8 +21,12 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef hash_map_h
 #define hash_map_h
 
-template<typename KeyId, typename Value,
-	 typename Traits>
+/* KeyId must be a trivial (POD) type.  Value may be non-trivial
+   (non-POD).  Ctors and dtors are invoked as necessary on
+   inserted and removed elements.  On hash_map destruction all
+   elements are removed.  */
+
+template<typename KeyId, typename Value, typename Traits>
 class GTY((user)) hash_map
 {
   typedef typename Traits::key_type Key;
@@ -151,12 +155,16 @@ public:
     {
       hash_entry *e = m_table.find_slot_with_hash (k, Traits::hash (k),
 						   INSERT);
-      bool existed = !hash_entry::is_empty (*e);
-      if (!existed)
-	e->m_key = k;
+      bool ins = hash_entry::is_empty (*e);
+      if (ins)
+	{
+	  e->m_key = k;
+	  new ((void *) &e->m_value) Value (v);
+	}
+      else
+	e->m_value = v;
 
-      e->m_value = v;
-      return existed;
+      return !ins;
     }
 
   /* if the passed in key is in the map return its value otherwise NULL.  */
@@ -168,8 +176,8 @@ public:
     }
 
   /* Return a reference to the value for the passed in key, creating the entry
-     if it doesn't already exist.  If existed is not NULL then it is set to false
-     if the key was not previously in the map, and true otherwise.  */
+     if it doesn't already exist.  If existed is not NULL then it is set to
+     false if the key was not previously in the map, and true otherwise.  */
 
   Value &get_or_insert (const Key &k, bool *existed = NULL)
     {
@@ -177,7 +185,10 @@ public:
 						   INSERT);
       bool ins = Traits::is_empty (*e);
       if (ins)
-	e->m_key = k;
+	{
+	  e->m_key = k;
+	  new ((void *)&e->m_value) Value ();
+	}
 
       if (existed != NULL)
 	*existed = !ins;
diff --git a/gcc/hash-set.h b/gcc/hash-set.h
index d891ed78297..5eb4bd768d9 100644
--- a/gcc/hash-set.h
+++ b/gcc/hash-set.h
@@ -21,6 +21,11 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef hash_set_h
 #define hash_set_h
 
+/* KeyId must be a trivial (POD) type.  Traits::value_type may be
+   non-trivial (non-POD).  Ctors and dtors are invoked as necessary
+   on inserted and removed elements.  On hash_set destruction all
+   elements are removed.  */
+
 template<typename KeyId, bool Lazy = false,
 	 typename Traits = default_hash_traits<KeyId> >
 class hash_set
@@ -48,7 +53,7 @@ public:
       Key *e = m_table.find_slot_with_hash (k, Traits::hash (k), INSERT);
       bool existed = !Traits::is_empty (*e);
       if (!existed)
-	*e = k;
+	new (e) Key (k);
 
       return existed;
     }
diff --git a/gcc/testsuite/gcc.c-torture/execute/return-addr.c b/gcc/testsuite/gcc.c-torture/execute/return-addr.c
new file mode 100644
index 00000000000..7981818b3be
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/return-addr.c
@@ -0,0 +1,122 @@
+/* Test to verify that a function that returns either the address
+   of a local variable or a non-local via a MAX_EXPR or MIN_EXPR
+   doesn't return null when the result of the expression is
+   the latter.  */
+
+#define NOIPA __attribute__ ((noclone, noinline, noipa))
+
+#define A(expr)                                                 \
+  ((expr)                                                       \
+   ? (void)0                                                    \
+   : (__builtin_printf ("assertion failed on line %i: %s\n",    \
+                        __LINE__, #expr),                       \
+      __builtin_abort ()))
+
+
+typedef __UINTPTR_TYPE__ uintptr_t;
+
+/* Return a bigger value than P.  The address still points (just
+   past) the local variable pointed to by P so the caller does
+   return the address of a local variable but that's hidden from
+   GCC by the attribute and the point of the test is to verify
+   that the address in the return statement in the caller isn't
+   replaced by null when GCC cannot prove the address doesn't
+   reference a non-local variable.  */
+
+NOIPA char* get_max_2 (char *p)
+{
+  return p + 1;
+}
+
+NOIPA char* get_max_3 (char *p, char *q)
+{
+  return p < q ? q + 1 : p + 1;
+}
+
+/* Analogous to the above.  The expressions are undefined because
+   they form an address prior to the beginning of the object but
+   it's hidden from GCC by the attributes.  */
+
+NOIPA char* get_min_2 (char *p)
+{
+  return p - 1;
+}
+
+NOIPA char* get_min_3 (char *p, char *q)
+{
+  return p < q ? p - 1 : q - 1;
+}
+
+
+NOIPA void* test_max_2 (void)
+{
+  char c;
+
+  char *p = get_max_2 (&c);
+
+  void *q = p > &c ? p : &c;  /* MAX_EXPR */
+  return q;
+}
+
+NOIPA void* test_max_3 (void)
+{
+  char c;
+  char d;
+
+  char *p = get_max_3 (&c, &d);
+
+  void *q = p < &c ? &c < &d ? &d : &c : p;
+  return q;
+}
+
+NOIPA void* test_min_2 (void)
+{
+  char c;
+
+  char *p = get_min_2 (&c);
+
+  void *q = p < &c ? p : &c;  /* MIN_EXPR" */
+  return q;
+}
+
+NOIPA void* test_min_3 (void)
+{
+  char c;
+  char d;
+
+  char *p = get_min_3 (&c, &d);
+
+  void *q = p > &c ? &c > &d ? &d : &c : p;
+  return q;
+}
+
+NOIPA void* test_min_3_phi (int i)
+{
+  char a, b;
+
+  char *p0 = &a;
+  char *p1 = &b;
+  char *p2 = get_min_3 (&a, &b);
+  char *p3 = get_min_3 (&a, &b);
+
+  char *p4 = p2 < p0 ? p2 : p0;
+  char *p5 = p3 < p1 ? p3 : p1;
+
+  __builtin_printf ("%p %p %p %p\n", p2, p3, p4, p5);
+
+  if (i == 1)
+    return p4;
+  else
+    return p5;
+}
+
+int main ()
+{
+  A (0 != test_max_2 ());
+  A (0 != test_max_3 ());
+
+  A (0 != test_min_2 ());
+  A (0 != test_min_3 ());
+
+  A (0 != test_min_3_phi (0));
+}
diff --git a/gcc/testsuite/gcc.dg/Walloca-4.c b/gcc/testsuite/gcc.dg/Walloca-4.c
index 85dcb7b9bb9..1fbed597b98 100644
--- a/gcc/testsuite/gcc.dg/Walloca-4.c
+++ b/gcc/testsuite/gcc.dg/Walloca-4.c
@@ -7,11 +7,12 @@
 {
 
   char *src;
- _Bool 
-      use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U);
- if (use_alloca)
+  _Bool use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U);
+  if (use_alloca)
     src = (char *) __builtin_alloca ((rear_ptr - w) * sizeof (char));
   else
     src = (char *) __builtin_malloc ((rear_ptr - w) * sizeof (char));
   return src;
 }
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c
new file mode 100644
index 00000000000..0e3435c8256
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c
@@ -0,0 +1,293 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+void sink (void*, ...);
+
+ATTR (noipa) void*
+return_alloca (int n)
+{
+  void *p = __builtin_alloca (n);
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_index_cst (int n)
+{
+  int *p = (int*)__builtin_alloca (n);
+  p = &p[1];
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_plus_cst (int n)
+{
+  int *p = (int*)__builtin_alloca (n);
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_plus_var (int n, int i)
+{
+  char *p = (char*)__builtin_alloca (n);
+  p += i;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_member_1 (int n)
+{
+  struct A *p = (struct A*)__builtin_alloca (n);
+  sink (&p->a);
+  return &p->a;     /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_member_2 (int n)
+{
+  struct A *p = (struct A*)__builtin_alloca (n);
+  sink (&p->b);
+  return &p->b;     /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_flexarray (int n)
+{
+  struct B *p = (struct B*)__builtin_alloca (n);
+  sink (p->c);
+  return p->c;      /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_array (void)
+{
+  int a[32];
+  void *p = a;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_index_cst (void)
+{
+  int a[32];
+  void *p = &a[2];
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_plus_cst (void)
+{
+  int a[32];
+  void *p = a + 2;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_plus_var (int i)
+{
+  int a[32];
+  void *p = a + i;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_member_1 (void)
+{
+  struct A a[2];
+  int *p = &a[1].a;
+  sink (a, p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_member_2 (void)
+{
+  struct A a[32];
+  int *p = &a[1].b;
+  sink (a, p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_vla (int n)
+{
+  char a[n];
+  void *p = a;
+  sink (p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_index_cst (int n)
+{
+  char a[n];
+  char *p = &a[3];
+  sink (p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_plus_cst (int n)
+{
+  char a[n];
+  char *p = a + 3;
+  sink (p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_index_var (int n, int i)
+{
+  char a[n];
+  char *p = &a[i];
+  sink (p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_plus_var (int n, int i)
+{
+  char a[n];
+  char *p = a + i;
+  sink (p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_member_1 (int n, int i)
+{
+  struct A a[n];
+  void *p = &a[i].a;
+  sink (a, p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_member_2 (int n, int i)
+{
+  struct A a[n];
+  void *p = &a[i].b;
+  sink (a, p);
+  return p;   /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_alloca_or_alloca (int n, int i)
+{
+  void *p = i ? __builtin_alloca (n * i) : __builtin_alloca (n);
+  sink (p);
+  /* The warning here should really be "function returns".  */
+  return p;   /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_or_alloca_2 (int n, int i)
+{
+  void *p0 = __builtin_alloca (n);
+  void *p1 = __builtin_alloca (n * 2);
+  void *p = i ? p0 : p1;
+  sink (p0, p1, p);
+  /* Same as above.  */
+  return p;   /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_or_array (int i)
+{
+  int a[5];
+  int b[7];
+  void *p = i ? a : b;
+  sink (a, b, p);
+  /* The warning here should really be "function returns".  */
+  return p;   /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_or_array_plus_var (int i, int j)
+{
+  int a[5];
+  int b[7];
+
+  void *p0 = a + i;
+  void *p1 = b + j;
+
+  void *p = i < j ? p0 : p1;
+  sink (a, b, p0, p1, p);
+  /* The warning here should really be "function returns".  */
+  return p;   /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+extern int global[32];
+
+ATTR (noipa) void*
+may_return_global_or_alloca (int n, int i)
+{
+  void *p = i ? global : __builtin_alloca (n);
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
+
+
+ATTR (noipa) void*
+may_return_global_or_alloca_plus_cst (int n, int i)
+{
+  int *p = i ? global : (int*)__builtin_alloca (n);
+  p += 7;
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_array (int n, int i)
+{
+  int a[32];
+  void *p = i ? global : a;
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_array_plus_cst (int n, int i)
+{
+  int a[32];
+  int *p = i ? global : a;
+  p += 4;
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_vla (int n, int i)
+{
+  int a[n];
+  void *p = i ? global : a;
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_vla_plus_cst (int n, int i)
+{
+  int a[n];
+  int *p = i ? global : a;
+  p += 4;
+  sink (p);
+  return p;   /* { dg-warning "function may return address of local" } */
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c
new file mode 100644
index 00000000000..6dad7af97e6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c
@@ -0,0 +1,248 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+typedef __INTPTR_TYPE__ intptr_t;
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+extern int g1[5], g2[5], g3[5], g4[5], g5[5];
+
+void sink (void*, ...);
+
+/* Verify that a pointer difference expression is handled correctly
+   even when converted to a pointer.  */
+
+ATTR (noipa) void*
+return_local_diff_cst (void)
+{
+  int a[5];
+  void *p = (void*)(&a[4] - &a[1]);
+  return p;
+}
+
+ATTR (noipa) void*
+return_local_diff_var (int i, int j)
+{
+  int a[5];
+  void *p = (void*)(&a[j] - &a[i]);
+  return p;
+}
+
+ATTR (noipa) void*
+return_2_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  void *p = i < 0 ? a : b;
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that returning the address of a local converted to intptr_t
+   is not diagnosed (see bug 90737 for a case the front-end gets wrong).  */
+
+ATTR (noipa) intptr_t
+return_int_2_locals (int i)
+{
+  int a[1];
+  int b[2];
+  void *p = i < 0 ? a : b;
+  return (intptr_t)p;
+}
+
+/* Verify that a conditional expression with a pointer first operand
+   is handled correctly.  */
+
+ATTR (noipa) void*
+return_2_locals_ptrcond (void *q)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  void *p = q ? a : b;
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that a preincrement expression with a pointer operand is
+   handled correctly.  */
+
+ATTR (noipa) void*
+return_2_locals_ptrinc (void *q)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int *p = q ? a : b;
+  return ++p;       /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  void *p = i < 0 ? a : 0 < i ? c : b;
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that a conditional expression with a pointer first operand
+   is handled correctly.  */
+
+ATTR (noipa) void*
+return_3_locals_ptrcond (void *p, void *q)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  void *r = q ? r ? a : b : c;
+  return r;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+  int e[5];         /* { dg-message "declared here" } */
+
+  void *p = i < -1 ? a : i < 0 ? b : 1 < i ? e : 0 < i ? d : c;
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+
+  void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? d : c;
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : c;
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_4_globals_1_local (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+
+  void *p = i < -1 ? a : i < 0 ? g1 : 1 < i ? g2 : 0 < i ? g4 : g3;
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_all_globals (int i)
+{
+  void *p = i < -1 ? g1 : i < 0 ? g2 : 1 < i ? g3 : 0 < i ? g5 : g4;
+  return p;
+}
+
+
+ATTR (noipa) void*
+return_2_alloca_local_cstoff (int n, int i)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *b = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *p = i < 0 ? a : b;
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_local_cstoff (int n, int i)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int b[2];                       /* { dg-message "declared here" } */
+  int *p = i < 0 ? a : b;
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_local_alloca_cstoff (int n, int i)
+{
+  int a[2];                       /* { dg-message "declared here" } */
+  int *b = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *p = i < 0 ? a : b;
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_cstoff (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int *p = i < 0 ? a : b;
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals_cstoff (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : c;
+  p += 1;
+  sink (p);
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_alloca_local_varoff (int n, int i, int j)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int b[2];                       /* { dg-message "declared here" } */
+
+  int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+  p += j;
+  sink (p);
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals_varoff (int i, int j)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+  p += j;
+  sink (p);
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c
new file mode 100644
index 00000000000..0a451efcaf0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c
@@ -0,0 +1,370 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+extern int g1[5], g2[5], g3[5], g4[5], g5[5];
+
+void sink (void*, ...);
+
+ATTR (noipa) void*
+return_2_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  void *p = b;
+  if (i < 0)
+    p = a;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_after_2_globals (int i, int j)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  int *p;
+  if (i < 0)
+    p = g1;
+  else
+    p = g2;
+
+  sink (p);
+
+  if (j < 0)
+    p = a;
+  else
+    p = b;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  void *p = b + 1;
+  if (i < 0)
+    p = a;
+  else if (0 < i)
+    p = c + 2;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+  int e[5];         /* { dg-message "declared here" } */
+
+  void *p = &c[2];
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = &b[1];
+  else if (1 < i)
+    p = &e[4];
+  else if (0 < i)
+    p = &d[3];
+
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals_switch (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+  int e[5];         /* { dg-message "declared here" } */
+
+  void *p = 0;
+
+  switch (i)
+    {
+    case 0: p = &a[1]; break;
+    case 1: p = &b[2]; break;
+    case 2: p = &c[3]; break;
+    case 3: p = &d[4]; break;
+    default: p = &e[5]; break;
+    }
+
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+
+  void *p = c;
+  if (i < -1)
+    sink (p = a);
+  else if (i < 0)
+    sink (p = b);
+  else if (1 < i)
+    sink (p = g1);
+  else if (0 < i)
+    sink (p = d);
+
+  sink (p, a, b, c, d);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals_switch (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+  int d[4];         /* { dg-message "declared here" } */
+
+  void *p = 0;
+
+  switch (i)
+    {
+    case 0: p = &a[0]; break;
+    case 1: p = &b[1]; break;
+    case 2: p = &c[2]; break;
+    case 3: p = &d[3]; break;
+    }
+
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  void *p = c;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = b;
+  else if (1 < i)
+    p = g1;
+  else if (0 < i)
+    p = g2;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  void *p = g3;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = b;
+  else if (1 < i)
+    p = g1;
+  else if (0 < i)
+    p = g2;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_4_globals_1_local (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+
+  void *p = g3;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = g1;
+  else if (1 < i)
+    p = g2;
+  else if (0 < i)
+    p = g4;
+
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_all_globals (int i)
+{
+  void *p = g4;
+  if (i < -1)
+    p = g1;
+  else if (i < 0)
+    p = g2;
+  else if (1 < i)
+    p = g3;
+  else if (0 < i)
+    p = g5;
+  return p;
+}
+
+
+ATTR (noipa) void*
+return_2_alloca_local_cstoff (int n, int i)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *b = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *p = i < 0 ? a : b;
+
+  p += 1;
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_local_cstoff (int n, int i)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int b[2];                       /* { dg-message "declared here" } */
+
+  int *p = b;
+  if (i < 0)
+    p = a;
+
+  p += 1;
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_local_alloca_cstoff (int n, int i)
+{
+  int a[2];                       /* { dg-message "declared here" } */
+  int *b = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int *p = b;
+  if (i < 0)
+    p = a;
+
+  p += 1;
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_cstoff (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  int *p = b;
+  if (i < 0)
+    p = a;
+
+  p += 1;
+  sink (p);
+
+  return p;         /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals_cstoff (int i)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+  int c[3];         /* { dg-message "declared here" } */
+
+  int *p = c;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = b;
+  else if (1 < i)
+    p = g1;
+  else if (0 < i)
+    p = g2;
+
+  p += 1;
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_alloca_local_varoff (int n, int i, int j)
+{
+  int *a = __builtin_alloca (n);  /* { dg-message "declared here" } */
+  int b[2];                       /* { dg-message "declared here" } */
+
+  int *p = g3;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = b;
+  else if (1 < i)
+    p = g1;
+  else if (0 < i)
+    p = g2;
+
+  p += j;
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals_varoff (int i, int j)
+{
+  int a[1];         /* { dg-message "declared here" } */
+  int b[2];         /* { dg-message "declared here" } */
+
+  int *p = g3;
+  if (i < -1)
+    p = a;
+  else if (i < 0)
+    p = b;
+  else if (1 < i)
+    p = g1;
+  else if (0 < i)
+    p = g2;
+
+  p += j;
+  sink (p);
+
+  return p;         /* { dg-warning "function may return address of local" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c
new file mode 100644
index 00000000000..bdf1cd40c1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c
@@ -0,0 +1,40 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+void sink (void*);
+
+void* loop_idx (int x)
+{
+  char a[32];       /* { dg-message "declared here" } */
+  char *p = a;
+
+  sink (a);
+
+  int i;
+  for (i = 0; i != 32; ++i)
+    if (p[i] == x)
+      break;
+
+  p = i < 32 ? &p[i] : 0;
+  return p;  /* { dg-warning "may return address of local variable" } */
+}
+
+
+void* loop_ptr (int i, int x)
+{
+  char a[32];       /* { dg-message "declared here" } */
+  char *p;
+
+  sink (a);
+
+  /* The warning for the statement below would ideally be a "returns"
+     because it definitely returns the address of a, but when both
+     returns get merged into one we end up with a "may return".  */
+  for (p = a; *p; ++p)
+    if (*p == x)
+      return p;     /* { dg-warning "(returns|may return) address of local variable" "missing location" { xfail *-*-* } } */
+  /* { dg-warning "(returns|may return) address of local variable" "pr90735" { target *-*-* } 0 } */
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c
new file mode 100644
index 00000000000..70138b3eff8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c
@@ -0,0 +1,203 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* memcpy (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+
+char* stpcpy (char*, const char*);
+char* stpncpy (char*, const char*, size_t);
+
+size_t strlen (const char*);
+size_t strnlen (const char*, size_t);
+
+char* strcat (char*, const char*);
+char* strncat (char*, const char*, size_t);
+
+char* strcpy (char*, const char*);
+char* strncpy (char*, const char*, size_t);
+
+char* strdup (const char*);
+
+char* strchr (const char*, int);
+char* strrchr (const char*, int);
+char* strstr (const char*, const char*);
+
+void sink (void*, ...);
+
+
+void* return_memcpy (const void *s, unsigned n)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  void *p = memcpy (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memcpy_cst (const void *s, unsigned n)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  void *p = memcpy (a + 1, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memcpy_var (const void *s, unsigned n, int i)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  void *p = memcpy (a + i, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_mempcpy (const void *s, unsigned n)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  void *p = mempcpy (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memmove_cst (unsigned n)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  void *p = memmove (a + 1, a, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memmove_var (unsigned n, int i)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  void *p = memmove (a + i, a, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_stpcpy (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = stpcpy (a, s);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_stpncpy (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = stpncpy (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcat (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strcat (a, s);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncat (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strncat (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+
+}
+char* return_strcpy (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = strcpy (a, s);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_plus_strlen (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = strcpy (a, s);
+  sink (p);
+  p += strlen (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_cst_plus_strlen (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strcpy (a + 1, s);
+  sink (p);
+  p += strlen (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_var_plus_strlen (unsigned n, const char *s, int i)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strcpy (a + i, s);
+  sink (p);
+  p += strlen (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncpy (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = strncpy (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncpy_plus_strnlen (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  char *p = strncpy (a, s, n);
+  p += strnlen (p, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strdup (unsigned n)
+{
+  char a[n];
+  sink (a);
+  char *p = strdup (a);
+  return p;
+}
+
+char* return_strchr (unsigned n, int c)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strchr (a, c);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strstr (unsigned n, const char *s)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strstr (a, s);
+  if (p)
+    p += strlen (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strrchr (unsigned n, int c)
+{
+  char a[n];                  /* { dg-message "declared here" } */
+  sink (a);
+  char *p = strrchr (a, c);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c
new file mode 100644
index 00000000000..ac1fb769ba8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c
@@ -0,0 +1,50 @@
+/* Test to verify that a PHI with a COND_EXPR argument in a return
+   statement is handled correctly.
+  { dg-do compile }
+  { dg-options "-O2 -Wall" } */
+
+extern struct S s;
+
+void* f (int n)
+{
+  void *p;
+  int x = 0;
+
+  for (int i = n; i >= 0; i--)
+    {
+      p = &s;
+      if (p == (void*)-1)
+         x = 1;
+      else if (p)
+         return p;
+    }
+
+  /* The return statement below ends up with the following IL:
+     <bb 6> [local count: 59055800]:
+     # x_10 = PHI <1(5), 0(2)>
+     _5 = x_10 != 0 ? -1B : 0B;
+
+     <bb 7> [local count: 114863532]:
+     # _3 = PHI <&s(4), _5(6), &s(3)>
+     return _3;  */
+  return x ? (void*)-1 : 0;
+}
+
+void* g (int n)
+{
+  void *p;
+  int x = 0;                  /* { dg-message "declared here" } */
+
+  for (int i = n; i >= 0; i--)
+    {
+      p = &s;
+      if (p == (void*)-1)
+         x = 1;
+      else if (p)
+         return p;
+    }
+
+  /* The return statement below does not reference a COND_EXPR argument.  */
+  return x ? &x : 0;          /* { dg-warning "may return address of local variable" "missing location" { xfail *-*-* } } */
+  /* { dg-warning "may return address of local variable" "pr90735" { target *-*-* } 0 } */
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c
new file mode 100644
index 00000000000..98a3805b5f0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c
@@ -0,0 +1,88 @@
+/* Test to verify that a MAX_EXPR and MIN_EXPR in a return statement
+   is handled correctly and that all local variables whose address
+   is or may be returned are identified.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+char* sink (char*, ...);
+
+void* test_max_2 (void)
+{
+  char c;                     /* { dg-message "declared here" } */
+
+  char *p = sink (&c);
+
+  void *q = p > &c ? p : &c;  /* MAX_EXPR */
+  return q;                   /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_max_3 (void)
+{
+  char c;                     /* { dg-message "declared here" } */
+  char d;                     /* { dg-message "declared here" } */
+
+  char *p = sink (&c, &d);
+
+  void *q = p < &c ? &c < &d ? &d : &c : p;
+  return q;                   /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_min_2 (void)
+{
+  char c;                     /* { dg-message "declared here" } */
+
+  char *p = sink (&c);
+
+  void *q = p < &c ? p : &c;  /* MIN_EXPR" */
+  return q;                   /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_min_3 (void)
+{
+  char c;                     /* { dg-message "declared here" } */
+  char d;                     /* { dg-message "declared here" } */
+
+  char *p = sink (&c, &d);
+
+  void *q = p > &c ? &c > &d ? &d : &c : p;
+  return q;                   /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_min_2_phi (int i)
+{
+  char a;                     /* { dg-message "declared here" } */
+
+  char *p = &a;
+  char *q = sink (&a);
+  p = p < q ? p : q;
+  if (i == 1)
+    return p;
+  /* { dg-warning "may return address of local variable" "missing location" { xfail *-*-* } } */
+  else
+    return q;
+}
+
+void* test_min_3_phi (int i)
+{
+  char a;                     /* { dg-message "declared here" } */
+  char b;                     /* { dg-message "declared here" } */
+
+  char *p0 = &a;
+  char *p1 = &b;
+  char *p2 = sink (&a, &b);
+  char *p3 = sink (&a, &b);
+
+  char *p4 = p2 < p0 ? p2 : p0;
+  char *p5 = p3 < p1 ? p3 : p1;
+
+  if (i == 1)
+    /* { dg-warning "may return address of local variable" "missing location" { xfail *-*-* } } */
+    return p4;
+  else
+    /* { dg-warning "may return address of local variable" "missing location" { xfail *-*-* } } */
+    return p5;
+}
+
+/* The directive below "swallows" warnings for both test_min_2_phi
+   and test_min_3_phi.
+  { dg-warning "may return address of local variable" "pr90735" { target *-*-* } 0 } */
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-9.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-9.c
new file mode 100644
index 00000000000..d24f911e9cd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-9.c
@@ -0,0 +1,73 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+   Test derived from gcc.c-torture/execute/20071108-1.c.  It shows
+   a false positive at -Os caused by the jump threading/vrp1 pass.
+   { dg-do compile }
+   { dg-options "-Os -fdump-tree-optimized" } */
+
+struct S
+{
+  int i;
+};
+
+void* f (void);
+
+__attribute__ ((noinline))
+struct S* g (int i)
+{
+  struct S *p = f (), q;
+
+  if (p == 0)
+    p = &q;
+
+  p->i = i;
+
+  if (p == &q)
+    p = 0;
+
+  /* With -Os the warning pass sees:
+
+       ...
+       <bb 4>
+       # p_1 = PHI <&q(2), p_5(3)>
+       p_1->i = i_6(D);
+       if (&q == p_1)
+         goto <bb 6>; [14.90%]
+       else
+         goto <bb 5>; [85.10%]
+
+       <bb 5>
+
+       <bb 6>
+       # p_2 = PHI <0B(4), p_1(5)>
+       q ={v} {CLOBBER};
+       return p_2;
+     }
+
+     which leads to:  */
+  return p;         /* { dg-bogus "may return address of local variable" "" { xfail *-*-* } } */
+
+  /* Whereas as -O2 the pass sees:
+
+       <bb 2>
+       p_5 = f ();
+       if (p_5 == 0B)
+         goto <bb 4>; [30.00%]
+       else
+         goto <bb 3>; [70.00%]
+
+       <bb 3>
+       # p_2 = PHI <0B(5), p_5(4)>
+       q ={v} {CLOBBER};
+       return p_2;
+
+       <bb 4>
+       p_5->i = i_6(D);
+       goto <bb 3>; [100.00%]
+
+       <bb 5>
+       q.i = i_6(D);
+       goto <bb 3>; [100.00%]
+     }
+
+     and no warning.  */
+}
diff --git a/gcc/testsuite/gcc.dg/pr41551.c b/gcc/testsuite/gcc.dg/pr41551.c
index 2f2ad2be97e..e1123206cc6 100644
--- a/gcc/testsuite/gcc.dg/pr41551.c
+++ b/gcc/testsuite/gcc.dg/pr41551.c
@@ -10,3 +10,5 @@ int main(void)
  int var, *p = &var;
  return (double)(uintptr_t)(p);
 }
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/pr59523.c b/gcc/testsuite/gcc.dg/pr59523.c
index a6c3302a683..49cbe5dd27a 100644
--- a/gcc/testsuite/gcc.dg/pr59523.c
+++ b/gcc/testsuite/gcc.dg/pr59523.c
@@ -16,3 +16,5 @@ foo (int a, int *b, int *c, int *d)
       r[i] = 1;
   return r;
 }
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/alias-37.c b/gcc/testsuite/gcc.dg/tree-ssa/alias-37.c
index 37eaaa66a10..6956209575a 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/alias-37.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/alias-37.c
@@ -11,7 +11,7 @@ int *foo (int bogus, int n)
     p = &a[2];
   else
     p = &i;
-  return p;
+  return p;         /* { dg-warning "\\\[-Wreturn-local-addr" } */
 }
 
 /* { dg-final { scan-tree-dump "Deleted dead store" "dse1" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
index 292ce6edefc..ed5df826432 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
@@ -41,3 +41,5 @@ f5 (void)
   int c[64] = {}, d[64] = {};
   return (__UINTPTR_TYPE__) &c[64] != (__UINTPTR_TYPE__) &d[0];
 }
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/winline-7.c b/gcc/testsuite/gcc.dg/winline-7.c
index 34deca42592..239d748926d 100644
--- a/gcc/testsuite/gcc.dg/winline-7.c
+++ b/gcc/testsuite/gcc.dg/winline-7.c
@@ -13,3 +13,5 @@ inline void *t (void)
 {
 	return q ();		 /* { dg-message "called from here" } */
 }
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/libgcc/generic-morestack.c b/libgcc/generic-morestack.c
index 0f6f0005f99..2dc373305fb 100644
--- a/libgcc/generic-morestack.c
+++ b/libgcc/generic-morestack.c
@@ -23,6 +23,8 @@ a copy of the GCC Runtime Library Exception along with this program;
 see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 <http://www.gnu.org/licenses/>.  */
 
+#pragma GCC optimize ("no-isolate-erroneous-paths-dereference")
+
 /* powerpc 32-bit not supported.  */
 #if !defined __powerpc__ || defined __powerpc64__
 

  reply	other threads:[~2019-06-30 21:50 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-22 21:34 Martin Sebor
2019-05-29 18:08 ` Jeff Law
2019-05-30 14:58   ` Martin Sebor
2019-05-30 15:21     ` Jeff Law
2019-05-30 15:48       ` Martin Sebor
2019-05-30 16:20         ` Jeff Law
2019-05-30 17:27           ` Jason Merrill
2019-05-31  0:26             ` Jeff Law
2019-05-30 23:35           ` Martin Sebor
2019-05-31 15:50             ` Jeff Law
2019-06-03  9:37               ` Richard Biener
2019-06-03 11:28                 ` Richard Biener
2019-06-04 11:45                   ` Richard Biener
2019-06-03 17:24                 ` Jeff Law
2019-05-31 21:19 ` Jeff Law
2019-06-03 23:24   ` Martin Sebor
2019-06-04 19:40     ` Martin Sebor
     [not found]       ` <224f8161-e370-bcbc-3ee6-5cff5835e016@redhat.com>
2019-06-19  3:19         ` Martin Sebor
2019-06-26 14:59           ` [PING] " Martin Sebor
2019-06-27  0:12           ` Jeff Law
2019-06-30 21:50             ` Martin Sebor [this message]
2019-07-02 20:59               ` Jeff Law
2019-07-11  6:45                 ` Martin Sebor

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e686e5c3-9110-a4dc-fa2a-7a88c98fe4d8@gmail.com \
    --to=msebor@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=law@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).