public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
@ 2019-05-22 21:34 Martin Sebor
  2019-05-29 18:08 ` Jeff Law
  2019-05-31 21:19 ` Jeff Law
  0 siblings, 2 replies; 23+ messages in thread
From: Martin Sebor @ 2019-05-22 21:34 UTC (permalink / raw)
  To: gcc-patches

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

-Wreturn-local-addr detects a subset of instances of returning
the address of a local object from a function but the warning
doesn't try to handle alloca or VLAs, or some non-trivial cases
of ordinary automatic variables[1].

The attached patch extends the implementation of the warning to
detect those.  It still doesn't detect instances where the address
is the result of a built-in such strcpy[2].

Tested on x86_64-linux.

Martin

[1] For example, this is only diagnosed with the patch:

   void* f (int i)
   {
     struct S { int a[2]; } s[2];
     return &s->a[i];
   }

[2] The following is not diagnosed even with the patch:

   void sink (void*);

   void* f (int i)
   {
     char a[6];
     char *p = __builtin_strcpy (a, "123");
     sink (p);
     return p;
   }

I would expect detecting to be possible and useful.  Maybe as
a follow-up.

[-- Attachment #2: gcc-71924.diff --]
[-- Type: text/x-patch, Size: 18765 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 c/71924
	* gimple-ssa-isolate-paths.c (is_addr_local): New function.
	(warn_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.

gcc/testsuite/ChangeLog:

	PR c/71924
	* gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
   return false;
 }
 
+/* Return true if EXPR is a expression of pointer type that refers
+   to the address of a variable with automatic storage duration.
+   If so, set *PLOC to the location of the object or the call that
+   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
+   also consider PHI statements and set *PMAYBE when some but not
+   all arguments of such statements refer to local variables, and
+   to clear it otherwise.  */
+
+static bool
+is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
+	       hash_set<gphi *> *visited = NULL)
+{
+  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 (TREE_OPERAND (baseaddr, 0), ploc, pmaybe, visited);
+
+      if ((!VAR_P (baseaddr)
+	   || is_global_var (baseaddr))
+	  && TREE_CODE (baseaddr) != PARM_DECL)
+	return false;
+
+      *ploc = DECL_SOURCE_LOCATION (baseaddr);
+      return true;
+    }
+
+  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 ptr = gimple_assign_rhs1 (def_stmt);
+	      return is_addr_local (ptr, ploc, pmaybe, visited);
+	    }
+	  return false;
+	}
+
+      if (code == GIMPLE_CALL
+	  && gimple_call_builtin_p (def_stmt))
+	{
+	  tree fn = gimple_call_fndecl (def_stmt);
+	  int code = DECL_FUNCTION_CODE (fn);
+	  if (code != BUILT_IN_ALLOCA
+	      && code != BUILT_IN_ALLOCA_WITH_ALIGN)
+	    return false;
+
+	  *ploc = gimple_location (def_stmt);
+	  return true;
+	}
+
+      if (code == GIMPLE_PHI && pmaybe)
+	{
+	  unsigned count = 0;
+	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
+
+	  unsigned nargs = gimple_phi_num_args (phi_stmt);
+	  for (unsigned i = 0; i < nargs; ++i)
+	    {
+	      if (!visited->add (phi_stmt))
+		{
+		  tree arg = gimple_phi_arg_def (phi_stmt, i);
+		  if (is_addr_local (arg, ploc, pmaybe, visited))
+		    ++count;
+		}
+	    }
+
+	  *pmaybe = count && count < nargs;
+	  return count != 0;
+	}
+    }
+
+  return false;
+}
+
+/* Detect and diagnose returning the address of a local variable in
+   a PHI result LHS and argument OP and PHI edge E in basic block BB.  */
+
+static basic_block
+warn_return_addr_local_phi_arg (basic_block bb, basic_block duplicate,
+				tree lhs, tree op, edge e)
+{
+  location_t origin;
+  if (!is_addr_local (op, &origin))
+    return NULL;
+
+  gimple *use_stmt;
+  imm_use_iterator iter;
+
+  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")
+	    && origin != UNKNOWN_LOCATION)
+	  inform (origin, "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.  */
+	  cfg_altered = true;
+	}
+    }
+
+  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.
 
@@ -400,58 +529,19 @@ find_implicit_erroneous_behavior (void)
 	    {
 	      tree op = gimple_phi_arg_def (phi, i);
 	      edge e = gimple_phi_arg_edge (phi, i);
-	      imm_use_iterator iter;
-	      gimple *use_stmt;
 
-	      next_i = i + 1;
-
-	      if (TREE_CODE (op) == ADDR_EXPR)
-		{
-		  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;
-			    }
-			}
-		    }
-		}
+	      duplicate = warn_return_addr_local_phi_arg (bb, duplicate,
+							  lhs, op, e);
+	      next_i = duplicate ? i : i + 1;
 
 	      if (!integer_zerop (op))
 		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)
@@ -482,6 +572,51 @@ find_implicit_erroneous_behavior (void)
     }
 }
 
+/* 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;
+
+  bool maybe = false;
+  location_t origin;
+  hash_set<gphi *> visited_phis;
+  if (!is_addr_local (val, &origin, &maybe, &visited_phis))
+    return;
+
+  /* We only need it for this particular case.  */
+  calculate_dominance_info (CDI_POST_DOMINATORS);
+  const char* msg = N_("function returns address of local variable");
+  if (maybe
+      || !dominated_by_p (CDI_POST_DOMINATORS,
+			  single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)), bb))
+      msg = N_("function may return address of local variable");
+
+  {
+    auto_diagnostic_group d;
+    if (warning_at (gimple_location (return_stmt),
+		    OPT_Wreturn_local_addr, msg)
+	&& origin != UNKNOWN_LOCATION)
+      inform (origin, "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 (return_stmt);
+    }
+}
+
 /* Look for statements which exhibit erroneous behavior.  For example
    a NULL pointer dereference.
 
@@ -525,49 +660,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/testsuite/gcc.dg/Walloca-4.c b/gcc/testsuite/gcc.dg/Walloca-4.c
index 85dcb7b9bb9..f888e2db2ed 100644
--- a/gcc/testsuite/gcc.dg/Walloca-4.c
+++ b/gcc/testsuite/gcc.dg/Walloca-4.c
@@ -7,7 +7,7 @@
 {
 
   char *src;
- _Bool 
+ _Bool
       use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U);
  if (use_alloca)
     src = (char *) __builtin_alloca ((rear_ptr - w) * sizeof (char));
@@ -15,3 +15,5 @@
     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/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/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" } */

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-22 21:34 [PATCH] warn on returning alloca and VLA (PR 71924, 90549) Martin Sebor
@ 2019-05-29 18:08 ` Jeff Law
  2019-05-30 14:58   ` Martin Sebor
  2019-05-31 21:19 ` Jeff Law
  1 sibling, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-05-29 18:08 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 5/22/19 3:34 PM, Martin Sebor wrote:
> -Wreturn-local-addr detects a subset of instances of returning
> the address of a local object from a function but the warning
> doesn't try to handle alloca or VLAs, or some non-trivial cases
> of ordinary automatic variables[1].
> 
> The attached patch extends the implementation of the warning to
> detect those.  It still doesn't detect instances where the address
> is the result of a built-in such strcpy[2].
> 
> Tested on x86_64-linux.
> 
> Martin
> 
> [1] For example, this is only diagnosed with the patch:
> 
>   void* f (int i)
>   {
>     struct S { int a[2]; } s[2];
>     return &s->a[i];
>   }
> 
> [2] The following is not diagnosed even with the patch:
> 
>   void sink (void*);
> 
>   void* f (int i)
>   {
>     char a[6];
>     char *p = __builtin_strcpy (a, "123");
>     sink (p);
>     return p;
>   }
> 
> I would expect detecting to be possible and useful.  Maybe as
> a follow-up.
> 
> 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 c/71924
> 	* gimple-ssa-isolate-paths.c (is_addr_local): New function.
> 	(warn_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.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/71924
> 	* gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
> --- a/gcc/gimple-ssa-isolate-paths.c
> +++ b/gcc/gimple-ssa-isolate-paths.c
> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
>    return false;
>  }
>  
> +/* Return true if EXPR is a expression of pointer type that refers
> +   to the address of a variable with automatic storage duration.
> +   If so, set *PLOC to the location of the object or the call that
> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
> +   also consider PHI statements and set *PMAYBE when some but not
> +   all arguments of such statements refer to local variables, and
> +   to clear it otherwise.  */
> +
> +static bool
> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
> +	       hash_set<gphi *> *visited = NULL)
> +{
> +  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 (TREE_OPERAND (baseaddr, 0), ploc, pmaybe, visited);
> +
> +      if ((!VAR_P (baseaddr)
> +	   || is_global_var (baseaddr))
> +	  && TREE_CODE (baseaddr) != PARM_DECL)
> +	return false;
> +
> +      *ploc = DECL_SOURCE_LOCATION (baseaddr);
> +      return true;
> +    }
> +
> +  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 ptr = gimple_assign_rhs1 (def_stmt);
> +	      return is_addr_local (ptr, ploc, pmaybe, visited);
> +	    }
> +	  return false;
> +	}
> +
> +      if (code == GIMPLE_CALL
> +	  && gimple_call_builtin_p (def_stmt))
> +	{
> +	  tree fn = gimple_call_fndecl (def_stmt);
> +	  int code = DECL_FUNCTION_CODE (fn);
> +	  if (code != BUILT_IN_ALLOCA
> +	      && code != BUILT_IN_ALLOCA_WITH_ALIGN)
> +	    return false;
> +
> +	  *ploc = gimple_location (def_stmt);
> +	  return true;
> +	}
> +
> +      if (code == GIMPLE_PHI && pmaybe)
> +	{
> +	  unsigned count = 0;
> +	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
> +
> +	  unsigned nargs = gimple_phi_num_args (phi_stmt);
> +	  for (unsigned i = 0; i < nargs; ++i)
> +	    {
> +	      if (!visited->add (phi_stmt))
> +		{
> +		  tree arg = gimple_phi_arg_def (phi_stmt, i);
> +		  if (is_addr_local (arg, ploc, pmaybe, visited))
> +		    ++count;
> +		}
> +	    }
> +
> +	  *pmaybe = count && count < nargs;
> +	  return count != 0;
> +	}
> +    }
> +
> +  return false;
> +}
Is there some reason you didn't query the alias oracle here?  It would
seem a fairly natural fit.  Ultimately given a pointer (which will be an
SSA_NAME) you want to ask whether or not it conclusively points into the
stack.

That would seem to dramatically simplify is_addr_local.

The rest looks pretty reasonable.

Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-29 18:08 ` Jeff Law
@ 2019-05-30 14:58   ` Martin Sebor
  2019-05-30 15:21     ` Jeff Law
  0 siblings, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-05-30 14:58 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 5/29/19 11:45 AM, Jeff Law wrote:
> On 5/22/19 3:34 PM, Martin Sebor wrote:
>> -Wreturn-local-addr detects a subset of instances of returning
>> the address of a local object from a function but the warning
>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>> of ordinary automatic variables[1].
>>
>> The attached patch extends the implementation of the warning to
>> detect those.  It still doesn't detect instances where the address
>> is the result of a built-in such strcpy[2].
>>
>> Tested on x86_64-linux.
>>
>> Martin
>>
>> [1] For example, this is only diagnosed with the patch:
>>
>>    void* f (int i)
>>    {
>>      struct S { int a[2]; } s[2];
>>      return &s->a[i];
>>    }
>>
>> [2] The following is not diagnosed even with the patch:
>>
>>    void sink (void*);
>>
>>    void* f (int i)
>>    {
>>      char a[6];
>>      char *p = __builtin_strcpy (a, "123");
>>      sink (p);
>>      return p;
>>    }
>>
>> I would expect detecting to be possible and useful.  Maybe as
>> a follow-up.
>>
>> 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 c/71924
>> 	* gimple-ssa-isolate-paths.c (is_addr_local): New function.
>> 	(warn_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.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR c/71924
>> 	* gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>> --- a/gcc/gimple-ssa-isolate-paths.c
>> +++ b/gcc/gimple-ssa-isolate-paths.c
>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
>>     return false;
>>   }
>>   
>> +/* Return true if EXPR is a expression of pointer type that refers
>> +   to the address of a variable with automatic storage duration.
>> +   If so, set *PLOC to the location of the object or the call that
>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>> +   also consider PHI statements and set *PMAYBE when some but not
>> +   all arguments of such statements refer to local variables, and
>> +   to clear it otherwise.  */
>> +
>> +static bool
>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>> +	       hash_set<gphi *> *visited = NULL)
>> +{
>> +  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 (TREE_OPERAND (baseaddr, 0), ploc, pmaybe, visited);
>> +
>> +      if ((!VAR_P (baseaddr)
>> +	   || is_global_var (baseaddr))
>> +	  && TREE_CODE (baseaddr) != PARM_DECL)
>> +	return false;
>> +
>> +      *ploc = DECL_SOURCE_LOCATION (baseaddr);
>> +      return true;
>> +    }
>> +
>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>> +	      return is_addr_local (ptr, ploc, pmaybe, visited);
>> +	    }
>> +	  return false;
>> +	}
>> +
>> +      if (code == GIMPLE_CALL
>> +	  && gimple_call_builtin_p (def_stmt))
>> +	{
>> +	  tree fn = gimple_call_fndecl (def_stmt);
>> +	  int code = DECL_FUNCTION_CODE (fn);
>> +	  if (code != BUILT_IN_ALLOCA
>> +	      && code != BUILT_IN_ALLOCA_WITH_ALIGN)
>> +	    return false;
>> +
>> +	  *ploc = gimple_location (def_stmt);
>> +	  return true;
>> +	}
>> +
>> +      if (code == GIMPLE_PHI && pmaybe)
>> +	{
>> +	  unsigned count = 0;
>> +	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
>> +
>> +	  unsigned nargs = gimple_phi_num_args (phi_stmt);
>> +	  for (unsigned i = 0; i < nargs; ++i)
>> +	    {
>> +	      if (!visited->add (phi_stmt))
>> +		{
>> +		  tree arg = gimple_phi_arg_def (phi_stmt, i);
>> +		  if (is_addr_local (arg, ploc, pmaybe, visited))
>> +		    ++count;
>> +		}
>> +	    }
>> +
>> +	  *pmaybe = count && count < nargs;
>> +	  return count != 0;
>> +	}
>> +    }
>> +
>> +  return false;
>> +}
> Is there some reason you didn't query the alias oracle here?  It would
> seem a fairly natural fit.  Ultimately given a pointer (which will be an
> SSA_NAME) you want to ask whether or not it conclusively points into the
> stack.
> 
> That would seem to dramatically simplify is_addr_local.

I did think about it but decided against changing the existing
design (iterating over PHI arguments), for a couple of reasons:

1) It feels like a bigger change than my simple "bug fix" calls
    for.
2) I'm not familiar enough with the alias oracle to say for sure
    if it can be used to give the same results as the existing
    implementation.  I.e., make it possible to identify and
    isolate each path that returns a local address (rather than
    just answering: yes, this pointer may point to some local
    in this function).

If the alias oracle can be used to give the same results without
excessive false positives then I think it would be fine to make
use of it.  Is that something you consider a prerequisite for
this change or should I look into it as a followup?

FWIW, I'm working on enhancing this to detect returning freed
pointers (under a different option).  That seems like a good
opportunity to also look into making use of the alias oracle.

Besides these enhancements, there's also a request to diagnose
dereferencing pointers to compound literals whose lifetime has
ended (PR 89990), or more generally, those to any such local
object.  It's about preventing essentially the same problem
(accessing bad data or corrupting others) but it seems that
both the analysis and the handling will be sufficiently
different to consider implementing it somewhere else.  What
are your thoughts on that?

Martin

> 
> The rest looks pretty reasonable.
> 
> Jeff
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 14:58   ` Martin Sebor
@ 2019-05-30 15:21     ` Jeff Law
  2019-05-30 15:48       ` Martin Sebor
  0 siblings, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-05-30 15:21 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 5/30/19 8:52 AM, Martin Sebor wrote:
> On 5/29/19 11:45 AM, Jeff Law wrote:
>> On 5/22/19 3:34 PM, Martin Sebor wrote:
>>> -Wreturn-local-addr detects a subset of instances of returning
>>> the address of a local object from a function but the warning
>>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>>> of ordinary automatic variables[1].
>>>
>>> The attached patch extends the implementation of the warning to
>>> detect those.  It still doesn't detect instances where the address
>>> is the result of a built-in such strcpy[2].
>>>
>>> Tested on x86_64-linux.
>>>
>>> Martin
>>>
>>> [1] For example, this is only diagnosed with the patch:
>>>
>>>    void* f (int i)
>>>    {
>>>      struct S { int a[2]; } s[2];
>>>      return &s->a[i];
>>>    }
>>>
>>> [2] The following is not diagnosed even with the patch:
>>>
>>>    void sink (void*);
>>>
>>>    void* f (int i)
>>>    {
>>>      char a[6];
>>>      char *p = __builtin_strcpy (a, "123");
>>>      sink (p);
>>>      return p;
>>>    }
>>>
>>> I would expect detecting to be possible and useful.  Maybe as
>>> a follow-up.
>>>
>>> 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 c/71924
>>>     * gimple-ssa-isolate-paths.c (is_addr_local): New function.
>>>     (warn_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.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR c/71924
>>>     * gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>>> --- a/gcc/gimple-ssa-isolate-paths.c
>>> +++ b/gcc/gimple-ssa-isolate-paths.c
>>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple
>>> *stmt)
>>>     return false;
>>>   }
>>>   +/* Return true if EXPR is a expression of pointer type that refers
>>> +   to the address of a variable with automatic storage duration.
>>> +   If so, set *PLOC to the location of the object or the call that
>>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>>> +   also consider PHI statements and set *PMAYBE when some but not
>>> +   all arguments of such statements refer to local variables, and
>>> +   to clear it otherwise.  */
>>> +
>>> +static bool
>>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>>> +           hash_set<gphi *> *visited = NULL)
>>> +{
>>> +  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 (TREE_OPERAND (baseaddr, 0), ploc, pmaybe,
>>> visited);
>>> +
>>> +      if ((!VAR_P (baseaddr)
>>> +       || is_global_var (baseaddr))
>>> +      && TREE_CODE (baseaddr) != PARM_DECL)
>>> +    return false;
>>> +
>>> +      *ploc = DECL_SOURCE_LOCATION (baseaddr);
>>> +      return true;
>>> +    }
>>> +
>>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>>> +          return is_addr_local (ptr, ploc, pmaybe, visited);
>>> +        }
>>> +      return false;
>>> +    }
>>> +
>>> +      if (code == GIMPLE_CALL
>>> +      && gimple_call_builtin_p (def_stmt))
>>> +    {
>>> +      tree fn = gimple_call_fndecl (def_stmt);
>>> +      int code = DECL_FUNCTION_CODE (fn);
>>> +      if (code != BUILT_IN_ALLOCA
>>> +          && code != BUILT_IN_ALLOCA_WITH_ALIGN)
>>> +        return false;
>>> +
>>> +      *ploc = gimple_location (def_stmt);
>>> +      return true;
>>> +    }
>>> +
>>> +      if (code == GIMPLE_PHI && pmaybe)
>>> +    {
>>> +      unsigned count = 0;
>>> +      gphi *phi_stmt = as_a <gphi *> (def_stmt);
>>> +
>>> +      unsigned nargs = gimple_phi_num_args (phi_stmt);
>>> +      for (unsigned i = 0; i < nargs; ++i)
>>> +        {
>>> +          if (!visited->add (phi_stmt))
>>> +        {
>>> +          tree arg = gimple_phi_arg_def (phi_stmt, i);
>>> +          if (is_addr_local (arg, ploc, pmaybe, visited))
>>> +            ++count;
>>> +        }
>>> +        }
>>> +
>>> +      *pmaybe = count && count < nargs;
>>> +      return count != 0;
>>> +    }
>>> +    }
>>> +
>>> +  return false;
>>> +}
>> Is there some reason you didn't query the alias oracle here?  It would
>> seem a fairly natural fit.  Ultimately given a pointer (which will be an
>> SSA_NAME) you want to ask whether or not it conclusively points into the
>> stack.
>>
>> That would seem to dramatically simplify is_addr_local.
> 
> I did think about it but decided against changing the existing
> design (iterating over PHI arguments), for a couple of reasons:
> 
> 1) It feels like a bigger change than my simple "bug fix" calls
>    for.
I suspect the net result will be simpler though ;-)  I think you just
get a pt solution and iterate over the things the solution says the
pointer can point to.


> 2) I'm not familiar enough with the alias oracle to say for sure
>    if it can be used to give the same results as the existing
>    implementation.  I.e., make it possible to identify and
>    isolate each path that returns a local address (rather than
>    just answering: yes, this pointer may point to some local
>    in this function).
Precision of the oracle is certainly the big question.


> 
> If the alias oracle can be used to give the same results without
> excessive false positives then I think it would be fine to make
> use of it.  Is that something you consider a prerequisite for
> this change or should I look into it as a followup?
I think we should explore it a bit before making a final decision.  It
may guide us for other work in this space (like detecting escaping
locals).   I think a dirty prototype to see if it's even in the right
ballpark would make sense.



> 
> FWIW, I'm working on enhancing this to detect returning freed
> pointers (under a different option).  That seems like a good
> opportunity to also look into making use of the alias oracle.
Yes.  I think there's two interesting cases here to ponder.  If we free
a pointer that must point to a local, then we can warn & trap.  If we
free a pointer that may point to a local, then we can only warn (unless
we can isolate the path).


> 
> Besides these enhancements, there's also a request to diagnose
> dereferencing pointers to compound literals whose lifetime has
> ended (PR 89990), or more generally, those to any such local
> object.  It's about preventing essentially the same problem
> (accessing bad data or corrupting others) but it seems that
> both the analysis and the handling will be sufficiently
> different to consider implementing it somewhere else.  What
> are your thoughts on that?
I think the tough problem here is we lose binding scopes as we lower
into gimple, so solving it in the general case may be tough.  We've
started adding some clobbers into the IL to denote object death points,
but I'm not sure if they're sufficient to implement this kind of warning.

Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 15:21     ` Jeff Law
@ 2019-05-30 15:48       ` Martin Sebor
  2019-05-30 16:20         ` Jeff Law
  0 siblings, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-05-30 15:48 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 5/30/19 9:13 AM, Jeff Law wrote:
> On 5/30/19 8:52 AM, Martin Sebor wrote:
>> On 5/29/19 11:45 AM, Jeff Law wrote:
>>> On 5/22/19 3:34 PM, Martin Sebor wrote:
>>>> -Wreturn-local-addr detects a subset of instances of returning
>>>> the address of a local object from a function but the warning
>>>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>>>> of ordinary automatic variables[1].
>>>>
>>>> The attached patch extends the implementation of the warning to
>>>> detect those.  It still doesn't detect instances where the address
>>>> is the result of a built-in such strcpy[2].
>>>>
>>>> Tested on x86_64-linux.
>>>>
>>>> Martin
>>>>
>>>> [1] For example, this is only diagnosed with the patch:
>>>>
>>>>     void* f (int i)
>>>>     {
>>>>       struct S { int a[2]; } s[2];
>>>>       return &s->a[i];
>>>>     }
>>>>
>>>> [2] The following is not diagnosed even with the patch:
>>>>
>>>>     void sink (void*);
>>>>
>>>>     void* f (int i)
>>>>     {
>>>>       char a[6];
>>>>       char *p = __builtin_strcpy (a, "123");
>>>>       sink (p);
>>>>       return p;
>>>>     }
>>>>
>>>> I would expect detecting to be possible and useful.  Maybe as
>>>> a follow-up.
>>>>
>>>> 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 c/71924
>>>>      * gimple-ssa-isolate-paths.c (is_addr_local): New function.
>>>>      (warn_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.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>>      PR c/71924
>>>>      * gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>>>> --- a/gcc/gimple-ssa-isolate-paths.c
>>>> +++ b/gcc/gimple-ssa-isolate-paths.c
>>>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple
>>>> *stmt)
>>>>      return false;
>>>>    }
>>>>    +/* Return true if EXPR is a expression of pointer type that refers
>>>> +   to the address of a variable with automatic storage duration.
>>>> +   If so, set *PLOC to the location of the object or the call that
>>>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>>>> +   also consider PHI statements and set *PMAYBE when some but not
>>>> +   all arguments of such statements refer to local variables, and
>>>> +   to clear it otherwise.  */
>>>> +
>>>> +static bool
>>>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>>>> +           hash_set<gphi *> *visited = NULL)
>>>> +{
>>>> +  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 (TREE_OPERAND (baseaddr, 0), ploc, pmaybe,
>>>> visited);
>>>> +
>>>> +      if ((!VAR_P (baseaddr)
>>>> +       || is_global_var (baseaddr))
>>>> +      && TREE_CODE (baseaddr) != PARM_DECL)
>>>> +    return false;
>>>> +
>>>> +      *ploc = DECL_SOURCE_LOCATION (baseaddr);
>>>> +      return true;
>>>> +    }
>>>> +
>>>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>>>> +          return is_addr_local (ptr, ploc, pmaybe, visited);
>>>> +        }
>>>> +      return false;
>>>> +    }
>>>> +
>>>> +      if (code == GIMPLE_CALL
>>>> +      && gimple_call_builtin_p (def_stmt))
>>>> +    {
>>>> +      tree fn = gimple_call_fndecl (def_stmt);
>>>> +      int code = DECL_FUNCTION_CODE (fn);
>>>> +      if (code != BUILT_IN_ALLOCA
>>>> +          && code != BUILT_IN_ALLOCA_WITH_ALIGN)
>>>> +        return false;
>>>> +
>>>> +      *ploc = gimple_location (def_stmt);
>>>> +      return true;
>>>> +    }
>>>> +
>>>> +      if (code == GIMPLE_PHI && pmaybe)
>>>> +    {
>>>> +      unsigned count = 0;
>>>> +      gphi *phi_stmt = as_a <gphi *> (def_stmt);
>>>> +
>>>> +      unsigned nargs = gimple_phi_num_args (phi_stmt);
>>>> +      for (unsigned i = 0; i < nargs; ++i)
>>>> +        {
>>>> +          if (!visited->add (phi_stmt))
>>>> +        {
>>>> +          tree arg = gimple_phi_arg_def (phi_stmt, i);
>>>> +          if (is_addr_local (arg, ploc, pmaybe, visited))
>>>> +            ++count;
>>>> +        }
>>>> +        }
>>>> +
>>>> +      *pmaybe = count && count < nargs;
>>>> +      return count != 0;
>>>> +    }
>>>> +    }
>>>> +
>>>> +  return false;
>>>> +}
>>> Is there some reason you didn't query the alias oracle here?  It would
>>> seem a fairly natural fit.  Ultimately given a pointer (which will be an
>>> SSA_NAME) you want to ask whether or not it conclusively points into the
>>> stack.
>>>
>>> That would seem to dramatically simplify is_addr_local.
>>
>> I did think about it but decided against changing the existing
>> design (iterating over PHI arguments), for a couple of reasons:
>>
>> 1) It feels like a bigger change than my simple "bug fix" calls
>>     for.
> I suspect the net result will be simpler though ;-)  I think you just
> get a pt solution and iterate over the things the solution says the
> pointer can point to.
> 
> 
>> 2) I'm not familiar enough with the alias oracle to say for sure
>>     if it can be used to give the same results as the existing
>>     implementation.  I.e., make it possible to identify and
>>     isolate each path that returns a local address (rather than
>>     just answering: yes, this pointer may point to some local
>>     in this function).
> Precision of the oracle is certainly the big question.
> 
> 
>>
>> If the alias oracle can be used to give the same results without
>> excessive false positives then I think it would be fine to make
>> use of it.  Is that something you consider a prerequisite for
>> this change or should I look into it as a followup?
> I think we should explore it a bit before making a final decision.  It
> may guide us for other work in this space (like detecting escaping
> locals).   I think a dirty prototype to see if it's even in the right
> ballpark would make sense.

Okay, let me look into it.

>> FWIW, I'm working on enhancing this to detect returning freed
>> pointers (under a different option).  That seems like a good
>> opportunity to also look into making use of the alias oracle.
> Yes.  I think there's two interesting cases here to ponder.  If we free
> a pointer that must point to a local, then we can warn & trap.  If we
> free a pointer that may point to a local, then we can only warn (unless
> we can isolate the path).

I wasn't actually thinking of freeing locals but it sounds like
a useful enhancement as well.  Thanks for the suggestion! :)

To be clear, what I'm working on is detecting:

   void* f (void *p)
   {
     free (p);   // note: pointer was freed here
     // ...
     return p;   // warning: returning a freed pointer
   }

>> Besides these enhancements, there's also a request to diagnose
>> dereferencing pointers to compound literals whose lifetime has
>> ended (PR 89990), or more generally, those to any such local
>> object.  It's about preventing essentially the same problem
>> (accessing bad data or corrupting others) but it seems that
>> both the analysis and the handling will be sufficiently
>> different to consider implementing it somewhere else.  What
>> are your thoughts on that?
> I think the tough problem here is we lose binding scopes as we lower
> into gimple, so solving it in the general case may be tough.  We've
> started adding some clobbers into the IL to denote object death points,
> but I'm not sure if they're sufficient to implement this kind of warning.

I was afraid that might be a problem.

Thanks
Martin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 15:48       ` Martin Sebor
@ 2019-05-30 16:20         ` Jeff Law
  2019-05-30 17:27           ` Jason Merrill
  2019-05-30 23:35           ` Martin Sebor
  0 siblings, 2 replies; 23+ messages in thread
From: Jeff Law @ 2019-05-30 16:20 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 5/30/19 9:34 AM, Martin Sebor wrote:

>>> If the alias oracle can be used to give the same results without
>>> excessive false positives then I think it would be fine to make
>>> use of it.  Is that something you consider a prerequisite for
>>> this change or should I look into it as a followup?
>> I think we should explore it a bit before making a final decision.  It
>> may guide us for other work in this space (like detecting escaping
>> locals).   I think a dirty prototype to see if it's even in the right
>> ballpark would make sense.
> 
> Okay, let me look into it.
Sounds good.  Again, go with a quick prototype to see if it's likely
feasible.  The tests you've added should dramatically help evaluating if
the oracle is up to the task.

> 
>>> FWIW, I'm working on enhancing this to detect returning freed
>>> pointers (under a different option).  That seems like a good
>>> opportunity to also look into making use of the alias oracle.
>> Yes.  I think there's two interesting cases here to ponder.  If we free
>> a pointer that must point to a local, then we can warn & trap.  If we
>> free a pointer that may point to a local, then we can only warn (unless
>> we can isolate the path).
> 
> I wasn't actually thinking of freeing locals but it sounds like
> a useful enhancement as well.  Thanks for the suggestion! :)
> 
> To be clear, what I'm working on is detecting:
> 
>   void* f (void *p)
>   {
>     free (p);   // note: pointer was freed here
>     // ...
>     return p;   // warning: returning a freed pointer
>   }
Ah, we were talking about different things. Though what you're doing
might be better modeled in a true global static analyzer as a
use-after-free problem.  My sense is that translation-unit local version
of that problem really isn't that useful in practice.  THough I guess
there isn't anything bad with having a TU local version.


> 
>>> Besides these enhancements, there's also a request to diagnose
>>> dereferencing pointers to compound literals whose lifetime has
>>> ended (PR 89990), or more generally, those to any such local
>>> object.  It's about preventing essentially the same problem
>>> (accessing bad data or corrupting others) but it seems that
>>> both the analysis and the handling will be sufficiently
>>> different to consider implementing it somewhere else.  What
>>> are your thoughts on that?
>> I think the tough problem here is we lose binding scopes as we lower
>> into gimple, so solving it in the general case may be tough.  We've
>> started adding some clobbers into the IL to denote object death points,
>> but I'm not sure if they're sufficient to implement this kind of warning.
> 
> I was afraid that might be a problem.
Way back in the early days of tree-ssa we kept the binding scopes.  But
that proved problematical in various ways.  Mostly they just got in the
way of analysis an optimization and we spent far too much time in the
optimizers working around them or removing those which were empty.

They'd be helpful in this kind of analysis, stack slot sharing vs the
inliner and a couple other problems.  I don't know if the pendulum has
moved far enough to revisit :-)

jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  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
  1 sibling, 1 reply; 23+ messages in thread
From: Jason Merrill @ 2019-05-30 17:27 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, gcc-patches

On Thu, May 30, 2019 at 12:16 PM Jeff Law <law@redhat.com> wrote:
>
> On 5/30/19 9:34 AM, Martin Sebor wrote:
>
> >>> If the alias oracle can be used to give the same results without
> >>> excessive false positives then I think it would be fine to make
> >>> use of it.  Is that something you consider a prerequisite for
> >>> this change or should I look into it as a followup?
> >> I think we should explore it a bit before making a final decision.  It
> >> may guide us for other work in this space (like detecting escaping
> >> locals).   I think a dirty prototype to see if it's even in the right
> >> ballpark would make sense.
> >
> > Okay, let me look into it.
> Sounds good.  Again, go with a quick prototype to see if it's likely
> feasible.  The tests you've added should dramatically help evaluating if
> the oracle is up to the task.
>
> >
> >>> FWIW, I'm working on enhancing this to detect returning freed
> >>> pointers (under a different option).  That seems like a good
> >>> opportunity to also look into making use of the alias oracle.
> >> Yes.  I think there's two interesting cases here to ponder.  If we free
> >> a pointer that must point to a local, then we can warn & trap.  If we
> >> free a pointer that may point to a local, then we can only warn (unless
> >> we can isolate the path).
> >
> > I wasn't actually thinking of freeing locals but it sounds like
> > a useful enhancement as well.  Thanks for the suggestion! :)
> >
> > To be clear, what I'm working on is detecting:
> >
> >   void* f (void *p)
> >   {
> >     free (p);   // note: pointer was freed here
> >     // ...
> >     return p;   // warning: returning a freed pointer
> >   }
> Ah, we were talking about different things. Though what you're doing
> might be better modeled in a true global static analyzer as a
> use-after-free problem.  My sense is that translation-unit local version
> of that problem really isn't that useful in practice.  THough I guess
> there isn't anything bad with having a TU local version.
>
>
> >
> >>> Besides these enhancements, there's also a request to diagnose
> >>> dereferencing pointers to compound literals whose lifetime has
> >>> ended (PR 89990), or more generally, those to any such local
> >>> object.  It's about preventing essentially the same problem
> >>> (accessing bad data or corrupting others) but it seems that
> >>> both the analysis and the handling will be sufficiently
> >>> different to consider implementing it somewhere else.  What
> >>> are your thoughts on that?
> >> I think the tough problem here is we lose binding scopes as we lower
> >> into gimple, so solving it in the general case may be tough.  We've
> >> started adding some clobbers into the IL to denote object death points,
> >> but I'm not sure if they're sufficient to implement this kind of warning.
> >
> > I was afraid that might be a problem.
> Way back in the early days of tree-ssa we kept the binding scopes.  But
> that proved problematical in various ways.  Mostly they just got in the
> way of analysis an optimization and we spent far too much time in the
> optimizers working around them or removing those which were empty.
>
> They'd be helpful in this kind of analysis, stack slot sharing vs the
> inliner and a couple other problems.  I don't know if the pendulum has
> moved far enough to revisit :-)

Why wouldn't clobbers be sufficient?

Jaason

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 16:20         ` Jeff Law
  2019-05-30 17:27           ` Jason Merrill
@ 2019-05-30 23:35           ` Martin Sebor
  2019-05-31 15:50             ` Jeff Law
  1 sibling, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-05-30 23:35 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 5/30/19 10:15 AM, Jeff Law wrote:
> On 5/30/19 9:34 AM, Martin Sebor wrote:
> 
>>>> If the alias oracle can be used to give the same results without
>>>> excessive false positives then I think it would be fine to make
>>>> use of it.  Is that something you consider a prerequisite for
>>>> this change or should I look into it as a followup?
>>> I think we should explore it a bit before making a final decision.  It
>>> may guide us for other work in this space (like detecting escaping
>>> locals).   I think a dirty prototype to see if it's even in the right
>>> ballpark would make sense.
>>
>> Okay, let me look into it.
> Sounds good.  Again, go with a quick prototype to see if it's likely
> feasible.  The tests you've added should dramatically help evaluating if
> the oracle is up to the task.

So to expand on what I said on the phone when we spoke: the problem
I quickly ran into with the prototype is that I wasn't able to find
a way to identify pointers to alloca/VLA storage.

In the the points-to solution for the pointer being returned they
both have the vars_contains_escaped_heap flag set.  That seems like
an omission that shouldn't be hard to fix, but on its own, I don't
think it would be sufficient.

In the IL a VLA is represented as a pointer to an array, but when
returning a pointer into a VLA (at some offset so it's an SSA_NAME),
the pointer's point-to solution doesn't include the VLA pointer or
(AFAICS) make it possible to tell even that it is a VLA.  For example
here:

   f (int n)
   {
     int * p;
     int[0:D.1912] * a.1;
     sizetype _1;
     void * saved_stack.3_3;
     sizetype _6;

     <bb 2> [local count: 1073741824]:
     saved_stack.3_3 = __builtin_stack_save ();
     _1 = (sizetype) n_2(D);
     _6 = _1 * 4;
     a.1_8 = __builtin_alloca_with_align (_6, 32);
     p_9 = a.1_8 + _6;
     __builtin_stack_restore (saved_stack.3_3);
     return p_9;
   }

p_9's solution's is:

   p_9, points-to vars: { D.1925 } (escaped, escaped heap)

I couldn't find out how to determine that D.1925 is a VLA (or even
what it is).  It's not among the function's local variables that
FOR_EACH_LOCAL_DECL iterates over.

By replacing the VLA in the test case with a call to malloc I get
this for the returned p_7:

   p_7, points-to NULL, points-to vars: { D.1916 } (escaped, escaped heap)

Again, I see no way to quickly tell that this pointer points into
the block returned from malloc.

I'm sure Richard will correct me if there is a simple way to do it
but I couldn't find one.

If there is a way to identify that the returned pointer is for
a VLA (or alloca), for parity with the current patch we also
need to find the location of the VLA declaration or the alloca
call so that the warning could point to it.  Unless there's
a straight path from the mysterious D.1925 above to that
decl/call statement, finding it would likely require some
sor of traversal.  At that point I wouldn't be surprised if
the complexity of the solution didn't approach or even exceed
the current approach.

Martin

>>>> FWIW, I'm working on enhancing this to detect returning freed
>>>> pointers (under a different option).  That seems like a good
>>>> opportunity to also look into making use of the alias oracle.
>>> Yes.  I think there's two interesting cases here to ponder.  If we free
>>> a pointer that must point to a local, then we can warn & trap.  If we
>>> free a pointer that may point to a local, then we can only warn (unless
>>> we can isolate the path).
>>
>> I wasn't actually thinking of freeing locals but it sounds like
>> a useful enhancement as well.  Thanks for the suggestion! :)
>>
>> To be clear, what I'm working on is detecting:
>>
>>    void* f (void *p)
>>    {
>>      free (p);   // note: pointer was freed here
>>      // ...
>>      return p;   // warning: returning a freed pointer
>>    }
> Ah, we were talking about different things. Though what you're doing
> might be better modeled in a true global static analyzer as a
> use-after-free problem.  My sense is that translation-unit local version
> of that problem really isn't that useful in practice.  THough I guess
> there isn't anything bad with having a TU local version.
> 
> 
>>
>>>> Besides these enhancements, there's also a request to diagnose
>>>> dereferencing pointers to compound literals whose lifetime has
>>>> ended (PR 89990), or more generally, those to any such local
>>>> object.  It's about preventing essentially the same problem
>>>> (accessing bad data or corrupting others) but it seems that
>>>> both the analysis and the handling will be sufficiently
>>>> different to consider implementing it somewhere else.  What
>>>> are your thoughts on that?
>>> I think the tough problem here is we lose binding scopes as we lower
>>> into gimple, so solving it in the general case may be tough.  We've
>>> started adding some clobbers into the IL to denote object death points,
>>> but I'm not sure if they're sufficient to implement this kind of warning.
>>
>> I was afraid that might be a problem.
> Way back in the early days of tree-ssa we kept the binding scopes.  But
> that proved problematical in various ways.  Mostly they just got in the
> way of analysis an optimization and we spent far too much time in the
> optimizers working around them or removing those which were empty.
> 
> They'd be helpful in this kind of analysis, stack slot sharing vs the
> inliner and a couple other problems.  I don't know if the pendulum has
> moved far enough to revisit :-)
> 
> jeff
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 17:27           ` Jason Merrill
@ 2019-05-31  0:26             ` Jeff Law
  0 siblings, 0 replies; 23+ messages in thread
From: Jeff Law @ 2019-05-31  0:26 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Martin Sebor, gcc-patches

On 5/30/19 11:23 AM, Jason Merrill wrote:
> On Thu, May 30, 2019 at 12:16 PM Jeff Law <law@redhat.com> wrote:
>>
>> On 5/30/19 9:34 AM, Martin Sebor wrote:
>>
>>>>> If the alias oracle can be used to give the same results without
>>>>> excessive false positives then I think it would be fine to make
>>>>> use of it.  Is that something you consider a prerequisite for
>>>>> this change or should I look into it as a followup?
>>>> I think we should explore it a bit before making a final decision.  It
>>>> may guide us for other work in this space (like detecting escaping
>>>> locals).   I think a dirty prototype to see if it's even in the right
>>>> ballpark would make sense.
>>>
>>> Okay, let me look into it.
>> Sounds good.  Again, go with a quick prototype to see if it's likely
>> feasible.  The tests you've added should dramatically help evaluating if
>> the oracle is up to the task.
>>
>>>
>>>>> FWIW, I'm working on enhancing this to detect returning freed
>>>>> pointers (under a different option).  That seems like a good
>>>>> opportunity to also look into making use of the alias oracle.
>>>> Yes.  I think there's two interesting cases here to ponder.  If we free
>>>> a pointer that must point to a local, then we can warn & trap.  If we
>>>> free a pointer that may point to a local, then we can only warn (unless
>>>> we can isolate the path).
>>>
>>> I wasn't actually thinking of freeing locals but it sounds like
>>> a useful enhancement as well.  Thanks for the suggestion! :)
>>>
>>> To be clear, what I'm working on is detecting:
>>>
>>>   void* f (void *p)
>>>   {
>>>     free (p);   // note: pointer was freed here
>>>     // ...
>>>     return p;   // warning: returning a freed pointer
>>>   }
>> Ah, we were talking about different things. Though what you're doing
>> might be better modeled in a true global static analyzer as a
>> use-after-free problem.  My sense is that translation-unit local version
>> of that problem really isn't that useful in practice.  THough I guess
>> there isn't anything bad with having a TU local version.
>>
>>
>>>
>>>>> Besides these enhancements, there's also a request to diagnose
>>>>> dereferencing pointers to compound literals whose lifetime has
>>>>> ended (PR 89990), or more generally, those to any such local
>>>>> object.  It's about preventing essentially the same problem
>>>>> (accessing bad data or corrupting others) but it seems that
>>>>> both the analysis and the handling will be sufficiently
>>>>> different to consider implementing it somewhere else.  What
>>>>> are your thoughts on that?
>>>> I think the tough problem here is we lose binding scopes as we lower
>>>> into gimple, so solving it in the general case may be tough.  We've
>>>> started adding some clobbers into the IL to denote object death points,
>>>> but I'm not sure if they're sufficient to implement this kind of warning.
>>>
>>> I was afraid that might be a problem.
>> Way back in the early days of tree-ssa we kept the binding scopes.  But
>> that proved problematical in various ways.  Mostly they just got in the
>> way of analysis an optimization and we spent far too much time in the
>> optimizers working around them or removing those which were empty.
>>
>> They'd be helpful in this kind of analysis, stack slot sharing vs the
>> inliner and a couple other problems.  I don't know if the pendulum has
>> moved far enough to revisit :-)
> 
> Why wouldn't clobbers be sufficient?
I haven't looked into the clobbers in any detail.  If we're aggressively
emitting them at the end of object life, then they may be sufficient to
start tackling these issues.

jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-30 23:35           ` Martin Sebor
@ 2019-05-31 15:50             ` Jeff Law
  2019-06-03  9:37               ` Richard Biener
  0 siblings, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-05-31 15:50 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 5/30/19 4:56 PM, Martin Sebor wrote:
> On 5/30/19 10:15 AM, Jeff Law wrote:
>> On 5/30/19 9:34 AM, Martin Sebor wrote:
>>
>>>>> If the alias oracle can be used to give the same results without
>>>>> excessive false positives then I think it would be fine to make
>>>>> use of it.  Is that something you consider a prerequisite for
>>>>> this change or should I look into it as a followup?
>>>> I think we should explore it a bit before making a final decision.  It
>>>> may guide us for other work in this space (like detecting escaping
>>>> locals).   I think a dirty prototype to see if it's even in the right
>>>> ballpark would make sense.
>>>
>>> Okay, let me look into it.
>> Sounds good.  Again, go with a quick prototype to see if it's likely
>> feasible.  The tests you've added should dramatically help evaluating if
>> the oracle is up to the task.
> 
> So to expand on what I said on the phone when we spoke: the problem
> I quickly ran into with the prototype is that I wasn't able to find
> a way to identify pointers to alloca/VLA storage.
Your analysis matches my very quick read of the aliasing code.  It may
be the case that the Steensgaard patent got in the way here.


> 
> In the the points-to solution for the pointer being returned they
> both have the vars_contains_escaped_heap flag set.  That seems like
> an omission that shouldn't be hard to fix, but on its own, I don't
> think it would be sufficient.
RIght.  In theory the result of an alloca call shouldn't alias anything
in the heap -- but there were some implementations of alloca that were
built on top of malloc (ugh).  That flag may be catering to that case.

But in the case of a __builtin_alloca that shouldn't apply.  Hmm.  That
ultimately might be a bug.



> 
> In the IL a VLA is represented as a pointer to an array, but when
> returning a pointer into a VLA (at some offset so it's an SSA_NAME),
> the pointer's point-to solution doesn't include the VLA pointer or
> (AFAICS) make it possible to tell even that it is a VLA.  For example
> here:
> 
>   f (int n)
>   {
>     int * p;
>     int[0:D.1912] * a.1;
>     sizetype _1;
>     void * saved_stack.3_3;
>     sizetype _6;
> 
>     <bb 2> [local count: 1073741824]:
>     saved_stack.3_3 = __builtin_stack_save ();
>     _1 = (sizetype) n_2(D);
>     _6 = _1 * 4;
>     a.1_8 = __builtin_alloca_with_align (_6, 32);
>     p_9 = a.1_8 + _6;
>     __builtin_stack_restore (saved_stack.3_3);
>     return p_9;
>   }
> 
> p_9's solution's is:
> 
>   p_9, points-to vars: { D.1925 } (escaped, escaped heap)
> 
> I couldn't find out how to determine that D.1925 is a VLA (or even
> what it is).  It's not among the function's local variables that
> FOR_EACH_LOCAL_DECL iterates over.
It's possible that decl was created internally as part of the alias
oracle's analysis.

See make_heapvar in tree-ssa-structalias.c
> 
> By replacing the VLA in the test case with a call to malloc I get
> this for the returned p_7:
> 
>   p_7, points-to NULL, points-to vars: { D.1916 } (escaped, escaped heap)
> 
> Again, I see no way to quickly tell that this pointer points into
> the block returned from malloc.
If there's a way to make that determination it'd have to be on the
variable since the pt_solution flag bits don't carry a storage class
directly.

You might try to get a handle on those decls and dump them to see if
there's anything easily identifiable.  But it may be easier to dig into
the code that creates them.  A real quick scan of the aliasing code also
shows the "variable_info" structure.  It's private to the aliasing code,
but might guide you at things to look at.

Regardless, I don't see an immediate path forward  using the oracle to
identify objects in the stack for your patch.  WHich is unfortunate.




Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-22 21:34 [PATCH] warn on returning alloca and VLA (PR 71924, 90549) Martin Sebor
  2019-05-29 18:08 ` Jeff Law
@ 2019-05-31 21:19 ` Jeff Law
  2019-06-03 23:24   ` Martin Sebor
  1 sibling, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-05-31 21:19 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 5/22/19 3:34 PM, Martin Sebor wrote:
> -Wreturn-local-addr detects a subset of instances of returning
> the address of a local object from a function but the warning
> doesn't try to handle alloca or VLAs, or some non-trivial cases
> of ordinary automatic variables[1].
> 
> The attached patch extends the implementation of the warning to
> detect those.  It still doesn't detect instances where the address
> is the result of a built-in such strcpy[2].
> 
> Tested on x86_64-linux.
> 
> Martin
> 
> [1] For example, this is only diagnosed with the patch:
> 
>   void* f (int i)
>   {
>     struct S { int a[2]; } s[2];
>     return &s->a[i];
>   }
> 
> [2] The following is not diagnosed even with the patch:
> 
>   void sink (void*);
> 
>   void* f (int i)
>   {
>     char a[6];
>     char *p = __builtin_strcpy (a, "123");
>     sink (p);
>     return p;
>   }
> 
> I would expect detecting to be possible and useful.  Maybe as
> a follow-up.
> 
> 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 c/71924
> 	* gimple-ssa-isolate-paths.c (is_addr_local): New function.
> 	(warn_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.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/71924
> 	* gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
> --- a/gcc/gimple-ssa-isolate-paths.c
> +++ b/gcc/gimple-ssa-isolate-paths.c
> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
>    return false;
>  }
>  
> +/* Return true if EXPR is a expression of pointer type that refers
> +   to the address of a variable with automatic storage duration.
> +   If so, set *PLOC to the location of the object or the call that
> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
> +   also consider PHI statements and set *PMAYBE when some but not
> +   all arguments of such statements refer to local variables, and
> +   to clear it otherwise.  */
> +
> +static bool
> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
> +	       hash_set<gphi *> *visited = NULL)
> +{
> +  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 ptr = gimple_assign_rhs1 (def_stmt);
> +	      return is_addr_local (ptr, ploc, pmaybe, visited);
> +	    }
> +	  return false;
> +	}
So this is going to recurse on the rhs1 of something like
POINTER_PLUS_EXPR, that's a good thing :-)   But isn't it non-selective
about the codes where we recurse?

Consider

  ptr = (cond) ? res1 : res2

I think we'll end up recursing on the condition rather than looking at
res1 and res2.


I suspect there are a very limited number of expression codes that
appear on the RHS where we'd want to recurse on one or both operands.

POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to recurse
on both and logically and the result), BIT_AND (maybe we masked off some
bits in an address).  That's probably about it :-)

Are there any other codes you've seen or think would be useful in
practice to recurse through?  I'd rather list them explicitly rather
than just recurse down through every rhs1 we encounter.



> +
> +      if (code == GIMPLE_PHI && pmaybe)
> +	{
> +	  unsigned count = 0;
> +	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
> +
> +	  unsigned nargs = gimple_phi_num_args (phi_stmt);
> +	  for (unsigned i = 0; i < nargs; ++i)
> +	    {
> +	      if (!visited->add (phi_stmt))
> +		{
> +		  tree arg = gimple_phi_arg_def (phi_stmt, i);
> +		  if (is_addr_local (arg, ploc, pmaybe, visited))
> +		    ++count;
> +		}
> +	    }
So imagine

p = phi (x1, x1, x2)

Where both x1 and x2 would pass is_addr_local.  I think this code would
incorrectly set pmaybe.

We process the first instance of x1, find it is local and bump count.
When we encounter the second instance, it's in our map and we do
nothing.  THen we process x2 and bump count again.  So count would be 2.
 But count is going to be less than nargs so we'll set *pmaybe to true.

ISTM you'd have to record the result for each phi argument and query
that to determine if you should bump the counter if you've already
visited the argument.

It also seems to me that you can break the loop as soon as you've got a
nonzero count and get a false return from is_addr_local.   Not sure if
that'll matter in practice or not.

The other approach here (and I'm not suggesting you implement it) would
be to use the propagation engine.  That's probably overkill here since
we'd probably end up computing a whole lot of things we don't need.  My
sense is an on-demand approach like you've done is probably less
computationally expensive.

jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-31 15:50             ` Jeff Law
@ 2019-06-03  9:37               ` Richard Biener
  2019-06-03 11:28                 ` Richard Biener
  2019-06-03 17:24                 ` Jeff Law
  0 siblings, 2 replies; 23+ messages in thread
From: Richard Biener @ 2019-06-03  9:37 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, gcc-patches

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

On Fri, May 31, 2019 at 5:35 PM Jeff Law <law@redhat.com> wrote:>
> On 5/30/19 4:56 PM, Martin Sebor wrote:
> > On 5/30/19 10:15 AM, Jeff Law wrote:
> >> On 5/30/19 9:34 AM, Martin Sebor wrote:
> >>
> >>>>> If the alias oracle can be used to give the same results without
> >>>>> excessive false positives then I think it would be fine to make
> >>>>> use of it.  Is that something you consider a prerequisite for
> >>>>> this change or should I look into it as a followup?
> >>>> I think we should explore it a bit before making a final decision.  It
> >>>> may guide us for other work in this space (like detecting escaping
> >>>> locals).   I think a dirty prototype to see if it's even in the right
> >>>> ballpark would make sense.
> >>>
> >>> Okay, let me look into it.
> >> Sounds good.  Again, go with a quick prototype to see if it's likely
> >> feasible.  The tests you've added should dramatically help evaluating if
> >> the oracle is up to the task.
> >
> > So to expand on what I said on the phone when we spoke: the problem
> > I quickly ran into with the prototype is that I wasn't able to find
> > a way to identify pointers to alloca/VLA storage.
> Your analysis matches my very quick read of the aliasing code.  It may
> be the case that the Steensgaard patent got in the way here.
>
> >
> > In the the points-to solution for the pointer being returned they
> > both have the vars_contains_escaped_heap flag set.  That seems like
> > an omission that shouldn't be hard to fix, but on its own, I don't
> > think it would be sufficient.
> RIght.  In theory the result of an alloca call shouldn't alias anything
> in the heap -- but there were some implementations of alloca that were
> built on top of malloc (ugh).  That flag may be catering to that case.
>
> But in the case of a __builtin_alloca that shouldn't apply.  Hmm.  That
> ultimately might be a bug.
>
> >
> > In the IL a VLA is represented as a pointer to an array, but when
> > returning a pointer into a VLA (at some offset so it's an SSA_NAME),
> > the pointer's point-to solution doesn't include the VLA pointer or
> > (AFAICS) make it possible to tell even that it is a VLA.  For example
> > here:
> >
> >   f (int n)
> >   {
> >     int * p;
> >     int[0:D.1912] * a.1;
> >     sizetype _1;
> >     void * saved_stack.3_3;
> >     sizetype _6;
> >
> >     <bb 2> [local count: 1073741824]:
> >     saved_stack.3_3 = __builtin_stack_save ();
> >     _1 = (sizetype) n_2(D);
> >     _6 = _1 * 4;
> >     a.1_8 = __builtin_alloca_with_align (_6, 32);
> >     p_9 = a.1_8 + _6;
> >     __builtin_stack_restore (saved_stack.3_3);
> >     return p_9;
> >   }
> >
> > p_9's solution's is:
> >
> >   p_9, points-to vars: { D.1925 } (escaped, escaped heap)
> >
> > I couldn't find out how to determine that D.1925 is a VLA (or even
> > what it is).  It's not among the function's local variables that
> > FOR_EACH_LOCAL_DECL iterates over.
> It's possible that decl was created internally as part of the alias
> oracle's analysis.

Yes.  Note that only the UID was reserved the fake decl doesn't
live on.

Note that for the testcase above the "local" alloca storage escapes
which means you run into a catch-22 here given points-to computes
a conservative correct solution and  you want to detect escaping
locals.  Usually detecting a pointer to local storage can be done
by using ptr_deref_may_alias_global_p but of course in this
case the storage was marked global by PTA itself (and our PTA
is not flow-sensitive and it doesn't distinguish an escape through
a return stmt from an escape through a call which is relevant
even for local storage).

Feature-wise the PTA solver is missing sth like a "filter"
we could put in front of return stmts that doesn't let
addresses of locals leak.  The simplest way of implementing
this might be to not include 'returns' in the constraints at all
(in non-IPA mode) and handle them by post-processing the
solver result.  That gets us some additional flow-sensitivity
and a way to filter locals.  Let me see if I can cook up this.

That may ultimatively also help the warning code where you
then can use ptr_deref_may_alias_global_p.

Sth like the attached - completely untested (the
is_global_var check is likely too simplistic...).  It does
the job on alloca for me.

p_5, points-to NULL, points-to vars: { D.1913 }
_6, points-to NULL, points-to vars: { D.1913 }

foo (int n)
{
  void * p;
  long unsigned int _1;
  void * _6;

  <bb 2> :
  _1 = (long unsigned int) n_2(D);
  p_5 = __builtin_alloca (_1);
  _6 = p_5;
  return p_5;


Richard.

> See make_heapvar in tree-ssa-structalias.c
> >
> > By replacing the VLA in the test case with a call to malloc I get
> > this for the returned p_7:
> >
> >   p_7, points-to NULL, points-to vars: { D.1916 } (escaped, escaped heap)
> >
> > Again, I see no way to quickly tell that this pointer points into
> > the block returned from malloc.
> If there's a way to make that determination it'd have to be on the
> variable since the pt_solution flag bits don't carry a storage class
> directly.
>
> You might try to get a handle on those decls and dump them to see if
> there's anything easily identifiable.  But it may be easier to dig into
> the code that creates them.  A real quick scan of the aliasing code also
> shows the "variable_info" structure.  It's private to the aliasing code,
> but might guide you at things to look at.
>
> Regardless, I don't see an immediate path forward  using the oracle to
> identify objects in the stack for your patch.  WHich is unfortunate.
>
>
>
>
> Jeff

[-- Attachment #2: p --]
[-- Type: application/octet-stream, Size: 2230 bytes --]

Index: gcc/tree-ssa-structalias.c
===================================================================
--- gcc/tree-ssa-structalias.c	(revision 271854)
+++ gcc/tree-ssa-structalias.c	(working copy)
@@ -43,6 +43,7 @@
 #include "stringpool.h"
 #include "attribs.h"
 #include "tree-ssa.h"
+#include "tree-cfg.h"
 
 /* The idea behind this analyzer is to generate set constraints from the
    program, then solve the resulting constraints in order to generate the
@@ -4976,7 +4977,12 @@ find_func_aliases (struct function *fn,
       greturn *return_stmt = as_a <greturn *> (t);
       fi = NULL;
       if (!in_ipa_mode
-	  || !(fi = get_vi_for_tree (fn->decl)))
+	  && TREE_CODE (gimple_return_retval (return_stmt)) == SSA_NAME)
+	{
+	  /* We handle simple returns by post-processing the solutions.  */
+	  ;
+	}
+      if (!(fi = get_vi_for_tree (fn->decl)))
 	make_escape_constraint (gimple_return_retval (return_stmt));
       else if (in_ipa_mode)
 	{
@@ -7304,6 +7310,33 @@ compute_points_to_sets (void)
   /* From the constraints compute the points-to sets.  */
   solve_constraints ();
 
+  /* Post-process solutions for escapes through returns.  */
+  edge_iterator ei;
+  edge e;
+  FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+    if (greturn *ret = safe_dyn_cast <greturn *> (last_stmt (e->src)))
+      {
+	tree val = gimple_return_retval (ret);
+	/* ???  Easy to handle aggregates as well, indirections
+	   with some complications.  Mind to fixup find_func_aliases
+	   as well.  */
+	if (!val || TREE_CODE (val) != SSA_NAME)
+	  continue;
+	/* returns happen last in non-IPA so they only influence
+	   the ESCAPED solution and we can filter local variables.  */
+	varinfo_t evi = get_varinfo (find (escaped_id));
+	varinfo_t vi = lookup_vi_for_tree (val);
+	vi = get_varinfo (find (vi->id));
+	bitmap_iterator bi;
+	unsigned i;
+	EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi)
+	  {
+	    varinfo_t pvi = get_varinfo (i);
+	    if (pvi->is_global_var)
+	      bitmap_set_bit (evi->solution, i);
+	  }
+      }
+
   /* Compute the points-to set for ESCAPED used for call-clobber analysis.  */
   cfun->gimple_df->escaped = find_what_var_points_to (cfun->decl,
 						      get_varinfo (escaped_id));

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  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
  1 sibling, 1 reply; 23+ messages in thread
From: Richard Biener @ 2019-06-03 11:28 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, gcc-patches

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

On Mon, Jun 3, 2019 at 11:37 AM Richard Biener
<richard.guenther@gmail.com> wrote:
>
> On Fri, May 31, 2019 at 5:35 PM Jeff Law <law@redhat.com> wrote:>
> > On 5/30/19 4:56 PM, Martin Sebor wrote:
> > > On 5/30/19 10:15 AM, Jeff Law wrote:
> > >> On 5/30/19 9:34 AM, Martin Sebor wrote:
> > >>
> > >>>>> If the alias oracle can be used to give the same results without
> > >>>>> excessive false positives then I think it would be fine to make
> > >>>>> use of it.  Is that something you consider a prerequisite for
> > >>>>> this change or should I look into it as a followup?
> > >>>> I think we should explore it a bit before making a final decision.  It
> > >>>> may guide us for other work in this space (like detecting escaping
> > >>>> locals).   I think a dirty prototype to see if it's even in the right
> > >>>> ballpark would make sense.
> > >>>
> > >>> Okay, let me look into it.
> > >> Sounds good.  Again, go with a quick prototype to see if it's likely
> > >> feasible.  The tests you've added should dramatically help evaluating if
> > >> the oracle is up to the task.
> > >
> > > So to expand on what I said on the phone when we spoke: the problem
> > > I quickly ran into with the prototype is that I wasn't able to find
> > > a way to identify pointers to alloca/VLA storage.
> > Your analysis matches my very quick read of the aliasing code.  It may
> > be the case that the Steensgaard patent got in the way here.
> >
> > >
> > > In the the points-to solution for the pointer being returned they
> > > both have the vars_contains_escaped_heap flag set.  That seems like
> > > an omission that shouldn't be hard to fix, but on its own, I don't
> > > think it would be sufficient.
> > RIght.  In theory the result of an alloca call shouldn't alias anything
> > in the heap -- but there were some implementations of alloca that were
> > built on top of malloc (ugh).  That flag may be catering to that case.
> >
> > But in the case of a __builtin_alloca that shouldn't apply.  Hmm.  That
> > ultimately might be a bug.
> >
> > >
> > > In the IL a VLA is represented as a pointer to an array, but when
> > > returning a pointer into a VLA (at some offset so it's an SSA_NAME),
> > > the pointer's point-to solution doesn't include the VLA pointer or
> > > (AFAICS) make it possible to tell even that it is a VLA.  For example
> > > here:
> > >
> > >   f (int n)
> > >   {
> > >     int * p;
> > >     int[0:D.1912] * a.1;
> > >     sizetype _1;
> > >     void * saved_stack.3_3;
> > >     sizetype _6;
> > >
> > >     <bb 2> [local count: 1073741824]:
> > >     saved_stack.3_3 = __builtin_stack_save ();
> > >     _1 = (sizetype) n_2(D);
> > >     _6 = _1 * 4;
> > >     a.1_8 = __builtin_alloca_with_align (_6, 32);
> > >     p_9 = a.1_8 + _6;
> > >     __builtin_stack_restore (saved_stack.3_3);
> > >     return p_9;
> > >   }
> > >
> > > p_9's solution's is:
> > >
> > >   p_9, points-to vars: { D.1925 } (escaped, escaped heap)
> > >
> > > I couldn't find out how to determine that D.1925 is a VLA (or even
> > > what it is).  It's not among the function's local variables that
> > > FOR_EACH_LOCAL_DECL iterates over.
> > It's possible that decl was created internally as part of the alias
> > oracle's analysis.
>
> Yes.  Note that only the UID was reserved the fake decl doesn't
> live on.
>
> Note that for the testcase above the "local" alloca storage escapes
> which means you run into a catch-22 here given points-to computes
> a conservative correct solution and  you want to detect escaping
> locals.  Usually detecting a pointer to local storage can be done
> by using ptr_deref_may_alias_global_p but of course in this
> case the storage was marked global by PTA itself (and our PTA
> is not flow-sensitive and it doesn't distinguish an escape through
> a return stmt from an escape through a call which is relevant
> even for local storage).
>
> Feature-wise the PTA solver is missing sth like a "filter"
> we could put in front of return stmts that doesn't let
> addresses of locals leak.  The simplest way of implementing
> this might be to not include 'returns' in the constraints at all
> (in non-IPA mode) and handle them by post-processing the
> solver result.  That gets us some additional flow-sensitivity
> and a way to filter locals.  Let me see if I can cook up this.
>
> That may ultimatively also help the warning code where you
> then can use ptr_deref_may_alias_global_p.
>
> Sth like the attached - completely untested (the
> is_global_var check is likely too simplistic...).  It does
> the job on alloca for me.
>
> p_5, points-to NULL, points-to vars: { D.1913 }
> _6, points-to NULL, points-to vars: { D.1913 }
>
> foo (int n)
> {
>   void * p;
>   long unsigned int _1;
>   void * _6;
>
>   <bb 2> :
>   _1 = (long unsigned int) n_2(D);
>   p_5 = __builtin_alloca (_1);
>   _6 = p_5;
>   return p_5;

I am testing the following fixed and more complete patch (still
the actual return values we process optimally is restricted).
I'm not sure whether it will really help real-world testcases
since the lack of flow-sensitivity in general and the lack of
distinguishing between escapes via calls vs. escapes via
return pessimizes things (escapes to global memory complicates
things there).  Also in IPA mode we cannot post-process
returns like in the hack^Wpatch, a "filter" op would need to be
added, complicating the solver.

Bootstrap / regtest running on x86_64-unknown-linux-gnu.

Richard.

2019-06-03  Richard Biener  <rguenther@suse.de>

        * tree-ssa-structalias.c: Include tree-cfg.h.
        (make_heapvar): Do not make heap vars artificial.
        (find_func_aliases_for_builtin_call): Handle stack allocation
        functions.
        (find_func_aliases): Delay processing of simple enough returns
        in non-IPA mode.
        (set_uids_in_ptset): Adjust.
        (find_what_var_points_to): Likewise.
        (compute_points_to_sets): Post-process return statements,
        amending the escaped solution.

        * gcc.dg/tree-ssa/alias-37.c: New testcase.

>
> Richard.
>
> > See make_heapvar in tree-ssa-structalias.c
> > >
> > > By replacing the VLA in the test case with a call to malloc I get
> > > this for the returned p_7:
> > >
> > >   p_7, points-to NULL, points-to vars: { D.1916 } (escaped, escaped heap)
> > >
> > > Again, I see no way to quickly tell that this pointer points into
> > > the block returned from malloc.
> > If there's a way to make that determination it'd have to be on the
> > variable since the pt_solution flag bits don't carry a storage class
> > directly.
> >
> > You might try to get a handle on those decls and dump them to see if
> > there's anything easily identifiable.  But it may be easier to dig into
> > the code that creates them.  A real quick scan of the aliasing code also
> > shows the "variable_info" structure.  It's private to the aliasing code,
> > but might guide you at things to look at.
> >
> > Regardless, I don't see an immediate path forward  using the oracle to
> > identify objects in the stack for your patch.  WHich is unfortunate.
> >
> >
> >
> >
> > Jeff

[-- Attachment #2: p2 --]
[-- Type: application/octet-stream, Size: 5578 bytes --]

2019-06-03  Richard Biener  <rguenther@suse.de>

	* tree-ssa-structalias.c: Include tree-cfg.h.
	(make_heapvar): Do not make heap vars artificial.
	(find_func_aliases_for_builtin_call): Handle stack allocation
	functions.
	(find_func_aliases): Delay processing of simple enough returns
	in non-IPA mode.
	(set_uids_in_ptset): Adjust.
	(find_what_var_points_to): Likewise.
	(compute_points_to_sets): Post-process return statements,
	amending the escaped solution.

	* gcc.dg/tree-ssa/alias-37.c: New testcase.

Index: gcc/tree-ssa-structalias.c
===================================================================
--- gcc/tree-ssa-structalias.c	(revision 271860)
+++ gcc/tree-ssa-structalias.c	(working copy)
@@ -43,6 +43,7 @@
 #include "stringpool.h"
 #include "attribs.h"
 #include "tree-ssa.h"
+#include "tree-cfg.h"
 
 /* The idea behind this analyzer is to generate set constraints from the
    program, then solve the resulting constraints in order to generate the
@@ -3854,7 +3855,6 @@ make_heapvar (const char *name, bool add
   DECL_EXTERNAL (heapvar) = 1;
 
   vi = new_var_info (heapvar, name, add_id);
-  vi->is_artificial_var = true;
   vi->is_heap_var = true;
   vi->is_unknown_size_var = true;
   vi->offset = 0;
@@ -4409,6 +4409,30 @@ find_func_aliases_for_builtin_call (stru
 	      process_constraint (new_constraint (*lhsp, ac));
 	  return true;
 	}
+      case BUILT_IN_STACK_SAVE:
+      case BUILT_IN_STACK_RESTORE:
+        /* Nothing interesting happens.  */
+        return true;
+      case BUILT_IN_ALLOCA:
+      case BUILT_IN_ALLOCA_WITH_ALIGN:
+      case BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX:
+	{
+	  tree ptr = gimple_call_lhs (t);
+	  get_constraint_for (ptr, &lhsc);
+	  varinfo_t vi = make_heapvar ("HEAP", true);
+	  /* Alloca storage is never global.  To exempt it from escaped
+	     handling make it a non-heap var.  */
+	  DECL_EXTERNAL (vi->decl) = 0;
+	  vi->is_global_var = 0;
+	  vi->is_heap_var = 0;
+	  struct constraint_expr tmpc;
+	  tmpc.var = vi->id;
+	  tmpc.offset = 0;
+	  tmpc.type = ADDRESSOF;
+	  rhsc.safe_push (tmpc);
+	  process_all_all_constraints (lhsc, rhsc);
+	  return true;
+	}
       case BUILT_IN_POSIX_MEMALIGN:
         {
 	  tree ptrptr = gimple_call_arg (t, 0);
@@ -4976,7 +5000,12 @@ find_func_aliases (struct function *fn,
       greturn *return_stmt = as_a <greturn *> (t);
       fi = NULL;
       if (!in_ipa_mode
-	  || !(fi = get_vi_for_tree (fn->decl)))
+	  && SSA_VAR_P (gimple_return_retval (return_stmt)))
+	{
+	  /* We handle simple returns by post-processing the solutions.  */
+	  ;
+	}
+      if (!(fi = get_vi_for_tree (fn->decl)))
 	make_escape_constraint (gimple_return_retval (return_stmt));
       else if (in_ipa_mode)
 	{
@@ -6422,9 +6451,7 @@ set_uids_in_ptset (bitmap into, bitmap f
     {
       varinfo_t vi = get_varinfo (i);
 
-      /* The only artificial variables that are allowed in a may-alias
-	 set are heap variables.  */
-      if (vi->is_artificial_var && !vi->is_heap_var)
+      if (vi->is_artificial_var)
 	continue;
 
       if (everything_escaped
@@ -6544,9 +6571,6 @@ find_what_var_points_to (tree fndecl, va
 	    }
 	  else if (vi->id == nonlocal_id)
 	    pt->nonlocal = 1;
-	  else if (vi->is_heap_var)
-	    /* We represent heapvars in the points-to set properly.  */
-	    ;
 	  else if (vi->id == string_id)
 	    /* Nobody cares - STRING_CSTs are read-only entities.  */
 	    ;
@@ -7304,6 +7328,39 @@ compute_points_to_sets (void)
   /* From the constraints compute the points-to sets.  */
   solve_constraints ();
 
+  /* Post-process solutions for escapes through returns.  */
+  edge_iterator ei;
+  edge e;
+  FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+    if (greturn *ret = safe_dyn_cast <greturn *> (last_stmt (e->src)))
+      {
+	tree val = gimple_return_retval (ret);
+	/* ???  Easy to handle simple indirections with some work.
+	   Arbitrary references like foo.bar.baz are more difficult
+	   (but conservatively easy enough with just looking at the base).
+	   Mind to fixup find_func_aliases as well.  */
+	if (!val || !SSA_VAR_P (val))
+	  continue;
+	/* returns happen last in non-IPA so they only influence
+	   the ESCAPED solution and we can filter local variables.  */
+	varinfo_t escaped_vi = get_varinfo (find (escaped_id));
+	varinfo_t vi = lookup_vi_for_tree (val);
+	for (; vi; vi = vi_next (vi))
+	  {
+	    varinfo_t part_vi = get_varinfo (find (vi->id));
+	    bitmap_iterator bi;
+	    unsigned i;
+	    EXECUTE_IF_SET_IN_BITMAP (part_vi->solution, 0, i, bi)
+	      {
+		varinfo_t pointed_to_vi = get_varinfo (i);
+		if (pointed_to_vi->is_global_var
+		    /* We delay marking of heap memory as global.  */
+		    || pointed_to_vi->is_heap_var)
+		  bitmap_set_bit (escaped_vi->solution, i);
+	      }
+	  }
+      }
+
   /* Compute the points-to set for ESCAPED used for call-clobber analysis.  */
   cfun->gimple_df->escaped = find_what_var_points_to (cfun->decl,
 						      get_varinfo (escaped_id));
Index: gcc/testsuite/gcc.dg/tree-ssa/alias-37.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/alias-37.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/tree-ssa/alias-37.c	(working copy)
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse1-details" } */
+
+int i;
+int *foo (int bogus, int n)
+{
+  int a[n];
+  a[2] = bogus; /* Should elide this store since a cannot escape.  */
+  int *p;
+  if (bogus)
+    p = &a[2];
+  else
+    p = &i;
+  return p;
+}
+
+/* { dg-final { scan-tree-dump "Deleted dead store" "dse1" } } */

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-03  9:37               ` Richard Biener
  2019-06-03 11:28                 ` Richard Biener
@ 2019-06-03 17:24                 ` Jeff Law
  1 sibling, 0 replies; 23+ messages in thread
From: Jeff Law @ 2019-06-03 17:24 UTC (permalink / raw)
  To: Richard Biener; +Cc: Martin Sebor, gcc-patches

On 6/3/19 3:37 AM, Richard Biener wrote:
> On Fri, May 31, 2019 at 5:35 PM Jeff Law <law@redhat.com> wrote:>
>> On 5/30/19 4:56 PM, Martin Sebor wrote:
>>> On 5/30/19 10:15 AM, Jeff Law wrote:
>>>> On 5/30/19 9:34 AM, Martin Sebor wrote:
>>>>
>>>>>>> If the alias oracle can be used to give the same results without
>>>>>>> excessive false positives then I think it would be fine to make
>>>>>>> use of it.  Is that something you consider a prerequisite for
>>>>>>> this change or should I look into it as a followup?
>>>>>> I think we should explore it a bit before making a final decision.  It
>>>>>> may guide us for other work in this space (like detecting escaping
>>>>>> locals).   I think a dirty prototype to see if it's even in the right
>>>>>> ballpark would make sense.
>>>>>
>>>>> Okay, let me look into it.
>>>> Sounds good.  Again, go with a quick prototype to see if it's likely
>>>> feasible.  The tests you've added should dramatically help evaluating if
>>>> the oracle is up to the task.
>>>
>>> So to expand on what I said on the phone when we spoke: the problem
>>> I quickly ran into with the prototype is that I wasn't able to find
>>> a way to identify pointers to alloca/VLA storage.
>> Your analysis matches my very quick read of the aliasing code.  It may
>> be the case that the Steensgaard patent got in the way here.
>>
>>>
>>> In the the points-to solution for the pointer being returned they
>>> both have the vars_contains_escaped_heap flag set.  That seems like
>>> an omission that shouldn't be hard to fix, but on its own, I don't
>>> think it would be sufficient.
>> RIght.  In theory the result of an alloca call shouldn't alias anything
>> in the heap -- but there were some implementations of alloca that were
>> built on top of malloc (ugh).  That flag may be catering to that case.
>>
>> But in the case of a __builtin_alloca that shouldn't apply.  Hmm.  That
>> ultimately might be a bug.
>>
>>>
>>> In the IL a VLA is represented as a pointer to an array, but when
>>> returning a pointer into a VLA (at some offset so it's an SSA_NAME),
>>> the pointer's point-to solution doesn't include the VLA pointer or
>>> (AFAICS) make it possible to tell even that it is a VLA.  For example
>>> here:
>>>
>>>   f (int n)
>>>   {
>>>     int * p;
>>>     int[0:D.1912] * a.1;
>>>     sizetype _1;
>>>     void * saved_stack.3_3;
>>>     sizetype _6;
>>>
>>>     <bb 2> [local count: 1073741824]:
>>>     saved_stack.3_3 = __builtin_stack_save ();
>>>     _1 = (sizetype) n_2(D);
>>>     _6 = _1 * 4;
>>>     a.1_8 = __builtin_alloca_with_align (_6, 32);
>>>     p_9 = a.1_8 + _6;
>>>     __builtin_stack_restore (saved_stack.3_3);
>>>     return p_9;
>>>   }
>>>
>>> p_9's solution's is:
>>>
>>>   p_9, points-to vars: { D.1925 } (escaped, escaped heap)
>>>
>>> I couldn't find out how to determine that D.1925 is a VLA (or even
>>> what it is).  It's not among the function's local variables that
>>> FOR_EACH_LOCAL_DECL iterates over.
>> It's possible that decl was created internally as part of the alias
>> oracle's analysis.
> 
> Yes.  Note that only the UID was reserved the fake decl doesn't
> live on.
> 
> Note that for the testcase above the "local" alloca storage escapes
> which means you run into a catch-22 here given points-to computes
> a conservative correct solution and  you want to detect escaping
> locals.  Usually detecting a pointer to local storage can be done
> by using ptr_deref_may_alias_global_p but of course in this
> case the storage was marked global by PTA itself (and our PTA
> is not flow-sensitive and it doesn't distinguish an escape through
> a return stmt from an escape through a call which is relevant
> even for local storage).
Good point.  The inability to tell why something escaped is another
significant hurdle.

> 
> Feature-wise the PTA solver is missing sth like a "filter"
> we could put in front of return stmts that doesn't let
> addresses of locals leak.  The simplest way of implementing
> this might be to not include 'returns' in the constraints at all
> (in non-IPA mode) and handle them by post-processing the
> solver result.  That gets us some additional flow-sensitivity
> and a way to filter locals.  Let me see if I can cook up this.
Another thought would be to somehow flag how the pointer escaped.  ie,
was it as an argument, return value or stored to memory?  Though in the
end that level of detail may not be useful, so collecting it may not be
worth the effort.

> 
> That may ultimatively also help the warning code where you
> then can use ptr_deref_may_alias_global_p.
Another thought would be to use the alias oracle to prune what we look
at.  If we ask the oracle and the value doesn't escape, then it's not
worth walking up the use-def chain to see where it came from.


Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-05-31 21:19 ` Jeff Law
@ 2019-06-03 23:24   ` Martin Sebor
  2019-06-04 19:40     ` Martin Sebor
  0 siblings, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-06-03 23:24 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

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

On 5/31/19 2:46 PM, Jeff Law wrote:
> On 5/22/19 3:34 PM, Martin Sebor wrote:
>> -Wreturn-local-addr detects a subset of instances of returning
>> the address of a local object from a function but the warning
>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>> of ordinary automatic variables[1].
>>
>> The attached patch extends the implementation of the warning to
>> detect those.  It still doesn't detect instances where the address
>> is the result of a built-in such strcpy[2].
>>
>> Tested on x86_64-linux.
>>
>> Martin
>>
>> [1] For example, this is only diagnosed with the patch:
>>
>>    void* f (int i)
>>    {
>>      struct S { int a[2]; } s[2];
>>      return &s->a[i];
>>    }
>>
>> [2] The following is not diagnosed even with the patch:
>>
>>    void sink (void*);
>>
>>    void* f (int i)
>>    {
>>      char a[6];
>>      char *p = __builtin_strcpy (a, "123");
>>      sink (p);
>>      return p;
>>    }
>>
>> I would expect detecting to be possible and useful.  Maybe as
>> a follow-up.
>>
>> 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 c/71924
>> 	* gimple-ssa-isolate-paths.c (is_addr_local): New function.
>> 	(warn_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.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR c/71924
>> 	* gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>> --- a/gcc/gimple-ssa-isolate-paths.c
>> +++ b/gcc/gimple-ssa-isolate-paths.c
>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
>>     return false;
>>   }
>>   
>> +/* Return true if EXPR is a expression of pointer type that refers
>> +   to the address of a variable with automatic storage duration.
>> +   If so, set *PLOC to the location of the object or the call that
>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>> +   also consider PHI statements and set *PMAYBE when some but not
>> +   all arguments of such statements refer to local variables, and
>> +   to clear it otherwise.  */
>> +
>> +static bool
>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>> +	       hash_set<gphi *> *visited = NULL)
>> +{
>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>> +	      return is_addr_local (ptr, ploc, pmaybe, visited);
>> +	    }
>> +	  return false;
>> +	}
> So this is going to recurse on the rhs1 of something like
> POINTER_PLUS_EXPR, that's a good thing :-)   But isn't it non-selective
> about the codes where we recurse?
> 
> Consider
> 
>    ptr = (cond) ? res1 : res2
> 
> I think we'll end up recursing on the condition rather than looking at
> res1 and res2.
> 
> 
> I suspect there are a very limited number of expression codes that
> appear on the RHS where we'd want to recurse on one or both operands.
> 
> POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to recurse
> on both and logically and the result), BIT_AND (maybe we masked off some
> bits in an address).  That's probably about it :-)
> 
> Are there any other codes you've seen or think would be useful in
> practice to recurse through?  I'd rather list them explicitly rather
> than just recurse down through every rhs1 we encounter.

I don't have a list of codes to test for.  I initially contemplated
enumerating them but in the end decided the pointer type check would
be sufficient.  I wouldn't expect a COND_EXPR here.  Don't they get
transformed into PHIs?  In all my tests they do and and running
the whole test suite with an assert that it doesn't come up doesn't
expose any either.  (I left the assert for COND_EXPR there.)  If
a COND_EXPR really can come up in a GIMPLE assignment here can you
please show me how so I can add a test for it?

I've added tests to exercise all C expressions that evaluate to
pointers.  I don't know of any others where what you bring up
should be a concern and I don't want to try to hardwire tests for
any that I can't to exercise in the testsuite or don't know how.
If you know of some I'm happy to add them and adjust the code.

>> +
>> +      if (code == GIMPLE_PHI && pmaybe)
>> +	{
>> +	  unsigned count = 0;
>> +	  gphi *phi_stmt = as_a <gphi *> (def_stmt);
>> +
>> +	  unsigned nargs = gimple_phi_num_args (phi_stmt);
>> +	  for (unsigned i = 0; i < nargs; ++i)
>> +	    {
>> +	      if (!visited->add (phi_stmt))
>> +		{
>> +		  tree arg = gimple_phi_arg_def (phi_stmt, i);
>> +		  if (is_addr_local (arg, ploc, pmaybe, visited))
>> +		    ++count;
>> +		}
>> +	    }
> So imagine
> 
> p = phi (x1, x1, x2)
> 
> Where both x1 and x2 would pass is_addr_local.  I think this code would
> incorrectly set pmaybe.

I suppose it would but this happens readily with or without my
patch, for example here:

   int* f (int i)
   {
     int a[2], b[2];
     int *p = i ? a : b;
     return p;
   }

We end up with two instances of "function may return address
of local variable," one for each local, because
find_implicit_erroneous_behavior only issues the "may return"
kind of warning.

I don't particularly like this -- that's why I added PMAYBE to
the new code.  But to avoid mission creep I'd decided not draw
the line there and avoid trying to fix the problem completely.
(I've enhanced this in the attached update.)

> 
> We process the first instance of x1, find it is local and bump count.
> When we encounter the second instance, it's in our map and we do
> nothing.  THen we process x2 and bump count again.  So count would be 2.
>   But count is going to be less than nargs so we'll set *pmaybe to true.
> 
> ISTM you'd have to record the result for each phi argument and query
> that to determine if you should bump the counter if you've already
> visited the argument.

I suspect that no solution will be perfect, at a minimum because
multiple return statements sometimes get merged into one, so what
in the source code is a single return that definitely returns
the address a local may end up merged with one that returns
a null (and triggering a maybe kind of warning).  I have also
seen warnings in some non-trivial test cases that suggest that
some return statements get split up into two where a "may return"
could turn into a "definitely does return."

> It also seems to me that you can break the loop as soon as you've got a
> nonzero count and get a false return from is_addr_local.   Not sure if
> that'll matter in practice or not.

This is only possible if we're willing to lose some detail (i.e.,
if we are willing to only point to one variable when returning
the address of two or more).  I don't find that very user-friendly.

To wrap up, while I would have preferred taking a simpler approach
at first and making bigger design changes later, I have redesigned
the warning for better accuracy as you suggested above.

The attached revised patch first collects the return statements
in a hash table along with the locations of the local variables
whose addresses each statement returns, and then issues just one
warning for each statement, listing all the locals it refers to
in subsequent notes.

In addition, since it was nearly trivial, I also added checking
for returning addresses of locals via calls to built-ins like
memcpy.

I have beefed up the test suite to exercise non-trivial functions
involving different kinds of expressions and statements, including
loops.  Except for one xfail due to missing location information
(bug 90735 - missing location in -Wreturn-local-addr on a function
with two return statements​) I haven't found any issues.
The improved warning does find many more problems than the current
solution.

> The other approach here (and I'm not suggesting you implement it) would
> be to use the propagation engine.  That's probably overkill here since
> we'd probably end up computing a whole lot of things we don't need.  My
> sense is an on-demand approach like you've done is probably less
> computationally expensive.

I'm certainly not opposed to making further improvements (as I
mentioned, I'd like to add checking for returning freed pointers
and freeing locals as you suggested).  But I would prefer to make
these in incremental steps rather than growing what was at first
meant to be just small bug fix/enhancement for alloca and VLAs.

The attached update has been tested on x86_64-linux.

Martin

[-- Attachment #2: gcc-71924.diff --]
[-- Type: text/x-patch, Size: 44701 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 (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.

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/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..eb8e754870f 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -341,6 +341,230 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
   return false;
 }
 
+/* Vector of locations of local variables/alloca calls returned from
+   a function.  */
+typedef auto_vec <location_t> locvec_t;
+
+/* Describes the property of a return statement that may return
+   the address of one or more local variables.  */
+struct args_loc_t
+{
+  /* 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.  */
+  locvec_t locvec;
+};
+
+/* 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;
+      if (warning_at (stmtloc, OPT_Wreturn_local_addr,
+		      (maybe || argsloc.nargs > argsloc.locvec.length ()
+		       ? G_("function may return address of local variable")
+		       : G_("function returns address of local variable"))))
+	{
+	  for (unsigned i = 0; i != argsloc.locvec.length (); ++i)
+	    inform (argsloc.locvec[i], "declared here");
+	}
+    }
+}
+
+/* Return true if EXPR is a expression of pointer type that refers
+   to the address of a variable with automatic storage duration.
+   If so, set *PLOC to the location of the object or the call that
+   allocated it (for alloca and VLAs).  When PLOCMAP is non-null,
+   also consider PHI statements 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, location_t *ploc,
+	       locmap_t *plocmap = NULL, hash_set<gphi *> *visited = NULL)
+{
+  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),
+			      ploc, plocmap, visited);
+
+      if ((!VAR_P (baseaddr)
+	   || is_global_var (baseaddr))
+	  && TREE_CODE (baseaddr) != PARM_DECL)
+	return false;
+
+      *ploc = 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);
+	      gcc_assert (code != COND_EXPR);
+
+	      tree ptr = gimple_assign_rhs1 (def_stmt);
+	      return is_addr_local (return_stmt, ptr, ploc, plocmap, visited);
+	    }
+	  return false;
+	}
+
+      if (code == GIMPLE_CALL
+	  && gimple_call_builtin_p (def_stmt))
+	{
+	  tree fn = gimple_call_fndecl (def_stmt);
+	  int code = DECL_FUNCTION_CODE (fn);
+	  if (code == BUILT_IN_ALLOCA
+	      || code == BUILT_IN_ALLOCA_WITH_ALIGN)
+	    {
+	      *ploc = gimple_location (def_stmt);
+	      return true;
+	    }
+
+	  if (gimple_call_num_args (def_stmt) < 1)
+	    return false;
+
+	  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_STRCPY:
+	    case BUILT_IN_STRCPY_CHK:
+	    case BUILT_IN_STRNCAT:
+	    case BUILT_IN_STRNCAT_CHK:
+	    case BUILT_IN_STRNCPY:
+	    case BUILT_IN_STRNCPY_CHK:
+	      /* Check both the argument and the return value.  */
+	      return (is_addr_local (return_stmt,
+				     gimple_call_arg (def_stmt, 0),
+				     ploc, plocmap, visited)
+		      || is_addr_local (return_stmt,
+					gimple_call_lhs (def_stmt),
+					ploc, 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 (return_stmt);
+	  if (argsloc->nargs < nargs)
+	    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, ploc, plocmap, visited))
+		{
+		  argsloc->locvec.safe_push (*ploc);
+		  ++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.
+   Call isolate_path for each returned address.
+   Return DUPLICATE on success and NULL when no such address was found.  */
+
+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)
+{
+  location_t origin;
+  if (!is_addr_local (NULL, arg, &origin))
+    return NULL;
+
+  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 location
+	 of the PHI argument to the map.  */
+      args_loc_t &argsloc = locmap.get_or_insert (use_stmt);
+      argsloc.nargs = nargs;
+      argsloc.locvec.safe_push (origin);
+
+      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.  */
+	  cfg_altered = true;
+	}
+    }
+
+  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 +576,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,6 +614,12 @@ 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.
 
@@ -395,63 +627,24 @@ find_implicit_erroneous_behavior (void)
 	     index, 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;
 
-	      next_i = i + 1;
+	      duplicate = handle_return_addr_local_phi_arg (bb, duplicate, lhs,
+							    arg, e, locmap,
+							    nargs);
+	      next_i = duplicate ? i : i + 1;
 
-	      if (TREE_CODE (op) == ADDR_EXPR)
-		{
-		  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;
-			    }
-			}
-		    }
-		}
-
-	      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 +673,52 @@ 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;
+
+  location_t origin;
+  locmap_t locmap;
+
+  locmap.put (return_stmt, args_loc_t ());
+
+  hash_set<gphi *> visited_phis;
+  if (!is_addr_local (return_stmt, val, &origin, &locmap, &visited_phis)
+      /* && !is_addr_freed (val, &origin, &maybe, &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);
+  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);
+
+  /* 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 +764,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/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..1a1d155f575
--- /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 address of local variable" "missing location" { xfail *-*-* } } */
+  /* { dg-warning "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..4c40cd276ec
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c
@@ -0,0 +1,162 @@
+/* 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);
+
+void sink (void*, ...);
+
+
+void* return_memcpy (const void *s, unsigned n)
+{
+  char a[n];
+  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];
+  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];
+  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];
+  void *p = mempcpy (a, s, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memmove_cst (unsigned n)
+{
+  char a[n];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  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];
+  char *p = strncpy (a, s, n);
+  p += strnlen (p, n);
+  sink (p);
+  return p;                   /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
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/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" } */

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-03 11:28                 ` Richard Biener
@ 2019-06-04 11:45                   ` Richard Biener
  0 siblings, 0 replies; 23+ messages in thread
From: Richard Biener @ 2019-06-04 11:45 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, gcc-patches

On Mon, Jun 3, 2019 at 1:27 PM Richard Biener
<richard.guenther@gmail.com> wrote:
>
> On Mon, Jun 3, 2019 at 11:37 AM Richard Biener
> <richard.guenther@gmail.com> wrote:
> >
> > On Fri, May 31, 2019 at 5:35 PM Jeff Law <law@redhat.com> wrote:>
> > > On 5/30/19 4:56 PM, Martin Sebor wrote:
> > > > On 5/30/19 10:15 AM, Jeff Law wrote:
> > > >> On 5/30/19 9:34 AM, Martin Sebor wrote:
> > > >>
> > > >>>>> If the alias oracle can be used to give the same results without
> > > >>>>> excessive false positives then I think it would be fine to make
> > > >>>>> use of it.  Is that something you consider a prerequisite for
> > > >>>>> this change or should I look into it as a followup?
> > > >>>> I think we should explore it a bit before making a final decision.  It
> > > >>>> may guide us for other work in this space (like detecting escaping
> > > >>>> locals).   I think a dirty prototype to see if it's even in the right
> > > >>>> ballpark would make sense.
> > > >>>
> > > >>> Okay, let me look into it.
> > > >> Sounds good.  Again, go with a quick prototype to see if it's likely
> > > >> feasible.  The tests you've added should dramatically help evaluating if
> > > >> the oracle is up to the task.
> > > >
> > > > So to expand on what I said on the phone when we spoke: the problem
> > > > I quickly ran into with the prototype is that I wasn't able to find
> > > > a way to identify pointers to alloca/VLA storage.
> > > Your analysis matches my very quick read of the aliasing code.  It may
> > > be the case that the Steensgaard patent got in the way here.
> > >
> > > >
> > > > In the the points-to solution for the pointer being returned they
> > > > both have the vars_contains_escaped_heap flag set.  That seems like
> > > > an omission that shouldn't be hard to fix, but on its own, I don't
> > > > think it would be sufficient.
> > > RIght.  In theory the result of an alloca call shouldn't alias anything
> > > in the heap -- but there were some implementations of alloca that were
> > > built on top of malloc (ugh).  That flag may be catering to that case.
> > >
> > > But in the case of a __builtin_alloca that shouldn't apply.  Hmm.  That
> > > ultimately might be a bug.
> > >
> > > >
> > > > In the IL a VLA is represented as a pointer to an array, but when
> > > > returning a pointer into a VLA (at some offset so it's an SSA_NAME),
> > > > the pointer's point-to solution doesn't include the VLA pointer or
> > > > (AFAICS) make it possible to tell even that it is a VLA.  For example
> > > > here:
> > > >
> > > >   f (int n)
> > > >   {
> > > >     int * p;
> > > >     int[0:D.1912] * a.1;
> > > >     sizetype _1;
> > > >     void * saved_stack.3_3;
> > > >     sizetype _6;
> > > >
> > > >     <bb 2> [local count: 1073741824]:
> > > >     saved_stack.3_3 = __builtin_stack_save ();
> > > >     _1 = (sizetype) n_2(D);
> > > >     _6 = _1 * 4;
> > > >     a.1_8 = __builtin_alloca_with_align (_6, 32);
> > > >     p_9 = a.1_8 + _6;
> > > >     __builtin_stack_restore (saved_stack.3_3);
> > > >     return p_9;
> > > >   }
> > > >
> > > > p_9's solution's is:
> > > >
> > > >   p_9, points-to vars: { D.1925 } (escaped, escaped heap)
> > > >
> > > > I couldn't find out how to determine that D.1925 is a VLA (or even
> > > > what it is).  It's not among the function's local variables that
> > > > FOR_EACH_LOCAL_DECL iterates over.
> > > It's possible that decl was created internally as part of the alias
> > > oracle's analysis.
> >
> > Yes.  Note that only the UID was reserved the fake decl doesn't
> > live on.
> >
> > Note that for the testcase above the "local" alloca storage escapes
> > which means you run into a catch-22 here given points-to computes
> > a conservative correct solution and  you want to detect escaping
> > locals.  Usually detecting a pointer to local storage can be done
> > by using ptr_deref_may_alias_global_p but of course in this
> > case the storage was marked global by PTA itself (and our PTA
> > is not flow-sensitive and it doesn't distinguish an escape through
> > a return stmt from an escape through a call which is relevant
> > even for local storage).
> >
> > Feature-wise the PTA solver is missing sth like a "filter"
> > we could put in front of return stmts that doesn't let
> > addresses of locals leak.  The simplest way of implementing
> > this might be to not include 'returns' in the constraints at all
> > (in non-IPA mode) and handle them by post-processing the
> > solver result.  That gets us some additional flow-sensitivity
> > and a way to filter locals.  Let me see if I can cook up this.
> >
> > That may ultimatively also help the warning code where you
> > then can use ptr_deref_may_alias_global_p.
> >
> > Sth like the attached - completely untested (the
> > is_global_var check is likely too simplistic...).  It does
> > the job on alloca for me.
> >
> > p_5, points-to NULL, points-to vars: { D.1913 }
> > _6, points-to NULL, points-to vars: { D.1913 }
> >
> > foo (int n)
> > {
> >   void * p;
> >   long unsigned int _1;
> >   void * _6;
> >
> >   <bb 2> :
> >   _1 = (long unsigned int) n_2(D);
> >   p_5 = __builtin_alloca (_1);
> >   _6 = p_5;
> >   return p_5;
>
> I am testing the following fixed and more complete patch (still
> the actual return values we process optimally is restricted).
> I'm not sure whether it will really help real-world testcases
> since the lack of flow-sensitivity in general and the lack of
> distinguishing between escapes via calls vs. escapes via
> return pessimizes things (escapes to global memory complicates
> things there).  Also in IPA mode we cannot post-process
> returns like in the hack^Wpatch, a "filter" op would need to be
> added, complicating the solver.
>
> Bootstrap / regtest running on x86_64-unknown-linux-gnu.

Needs another iteration since it miscompiles gengtype and

struct S { int *mem; };

struct S * __attribute__((noinline,noipa))
foo ()
{
  struct S *s = __builtin_malloc (sizeof (struct S));
  s->mem = __builtin_malloc (sizeof (int));
  s->mem[0] = 1;
  return s;
}

int
main()
{
  struct S *s = foo();
  if (s->mem[0] != 1)
    __builtin_abort ();
  return 0;
}

Richard.

> Richard.
>
> 2019-06-03  Richard Biener  <rguenther@suse.de>
>
>         * tree-ssa-structalias.c: Include tree-cfg.h.
>         (make_heapvar): Do not make heap vars artificial.
>         (find_func_aliases_for_builtin_call): Handle stack allocation
>         functions.
>         (find_func_aliases): Delay processing of simple enough returns
>         in non-IPA mode.
>         (set_uids_in_ptset): Adjust.
>         (find_what_var_points_to): Likewise.
>         (compute_points_to_sets): Post-process return statements,
>         amending the escaped solution.
>
>         * gcc.dg/tree-ssa/alias-37.c: New testcase.
>
> >
> > Richard.
> >
> > > See make_heapvar in tree-ssa-structalias.c
> > > >
> > > > By replacing the VLA in the test case with a call to malloc I get
> > > > this for the returned p_7:
> > > >
> > > >   p_7, points-to NULL, points-to vars: { D.1916 } (escaped, escaped heap)
> > > >
> > > > Again, I see no way to quickly tell that this pointer points into
> > > > the block returned from malloc.
> > > If there's a way to make that determination it'd have to be on the
> > > variable since the pt_solution flag bits don't carry a storage class
> > > directly.
> > >
> > > You might try to get a handle on those decls and dump them to see if
> > > there's anything easily identifiable.  But it may be easier to dig into
> > > the code that creates them.  A real quick scan of the aliasing code also
> > > shows the "variable_info" structure.  It's private to the aliasing code,
> > > but might guide you at things to look at.
> > >
> > > Regardless, I don't see an immediate path forward  using the oracle to
> > > identify objects in the stack for your patch.  WHich is unfortunate.
> > >
> > >
> > >
> > >
> > > Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-03 23:24   ` Martin Sebor
@ 2019-06-04 19:40     ` Martin Sebor
       [not found]       ` <224f8161-e370-bcbc-3ee6-5cff5835e016@redhat.com>
  0 siblings, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-06-04 19:40 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 6/3/19 5:24 PM, Martin Sebor wrote:
> On 5/31/19 2:46 PM, Jeff Law wrote:
>> On 5/22/19 3:34 PM, Martin Sebor wrote:
>>> -Wreturn-local-addr detects a subset of instances of returning
>>> the address of a local object from a function but the warning
>>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>>> of ordinary automatic variables[1].
>>>
>>> The attached patch extends the implementation of the warning to
>>> detect those.  It still doesn't detect instances where the address
>>> is the result of a built-in such strcpy[2].
>>>
>>> Tested on x86_64-linux.
>>>
>>> Martin
>>>
>>> [1] For example, this is only diagnosed with the patch:
>>>
>>>    void* f (int i)
>>>    {
>>>      struct S { int a[2]; } s[2];
>>>      return &s->a[i];
>>>    }
>>>
>>> [2] The following is not diagnosed even with the patch:
>>>
>>>    void sink (void*);
>>>
>>>    void* f (int i)
>>>    {
>>>      char a[6];
>>>      char *p = __builtin_strcpy (a, "123");
>>>      sink (p);
>>>      return p;
>>>    }
>>>
>>> I would expect detecting to be possible and useful.  Maybe as
>>> a follow-up.
>>>
>>> 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 c/71924
>>>     * gimple-ssa-isolate-paths.c (is_addr_local): New function.
>>>     (warn_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.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR c/71924
>>>     * gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>>> --- a/gcc/gimple-ssa-isolate-paths.c
>>> +++ b/gcc/gimple-ssa-isolate-paths.c
>>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple 
>>> *stmt)
>>>     return false;
>>>   }
>>> +/* Return true if EXPR is a expression of pointer type that refers
>>> +   to the address of a variable with automatic storage duration.
>>> +   If so, set *PLOC to the location of the object or the call that
>>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>>> +   also consider PHI statements and set *PMAYBE when some but not
>>> +   all arguments of such statements refer to local variables, and
>>> +   to clear it otherwise.  */
>>> +
>>> +static bool
>>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>>> +           hash_set<gphi *> *visited = NULL)
>>> +{
>>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>>> +          return is_addr_local (ptr, ploc, pmaybe, visited);
>>> +        }
>>> +      return false;
>>> +    }
>> So this is going to recurse on the rhs1 of something like
>> POINTER_PLUS_EXPR, that's a good thing :-)   But isn't it non-selective
>> about the codes where we recurse?
>>
>> Consider
>>
>>    ptr = (cond) ? res1 : res2
>>
>> I think we'll end up recursing on the condition rather than looking at
>> res1 and res2.
>>
>>
>> I suspect there are a very limited number of expression codes that
>> appear on the RHS where we'd want to recurse on one or both operands.
>>
>> POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to recurse
>> on both and logically and the result), BIT_AND (maybe we masked off some
>> bits in an address).  That's probably about it :-)
>>
>> Are there any other codes you've seen or think would be useful in
>> practice to recurse through?  I'd rather list them explicitly rather
>> than just recurse down through every rhs1 we encounter.
> 
> I don't have a list of codes to test for.  I initially contemplated
> enumerating them but in the end decided the pointer type check would
> be sufficient.  I wouldn't expect a COND_EXPR here.  Don't they get
> transformed into PHIs?  In all my tests they do and and running
> the whole test suite with an assert that it doesn't come up doesn't
> expose any either.  (I left the assert for COND_EXPR there.)  If
> a COND_EXPR really can come up in a GIMPLE assignment here can you
> please show me how so I can add a test for it?
> 
> I've added tests to exercise all C expressions that evaluate to
> pointers.  I don't know of any others where what you bring up
> should be a concern and I don't want to try to hardwire tests for
> any that I can't to exercise in the testsuite or don't know how.
> If you know of some I'm happy to add them and adjust the code.
> 
>>> +
>>> +      if (code == GIMPLE_PHI && pmaybe)
>>> +    {
>>> +      unsigned count = 0;
>>> +      gphi *phi_stmt = as_a <gphi *> (def_stmt);
>>> +
>>> +      unsigned nargs = gimple_phi_num_args (phi_stmt);
>>> +      for (unsigned i = 0; i < nargs; ++i)
>>> +        {
>>> +          if (!visited->add (phi_stmt))
>>> +        {
>>> +          tree arg = gimple_phi_arg_def (phi_stmt, i);
>>> +          if (is_addr_local (arg, ploc, pmaybe, visited))
>>> +            ++count;
>>> +        }
>>> +        }
>> So imagine
>>
>> p = phi (x1, x1, x2)
>>
>> Where both x1 and x2 would pass is_addr_local.  I think this code would
>> incorrectly set pmaybe.
> 
> I suppose it would but this happens readily with or without my
> patch, for example here:
> 
>    int* f (int i)
>    {
>      int a[2], b[2];
>      int *p = i ? a : b;
>      return p;
>    }
> 
> We end up with two instances of "function may return address
> of local variable," one for each local, because
> find_implicit_erroneous_behavior only issues the "may return"
> kind of warning.
> 
> I don't particularly like this -- that's why I added PMAYBE to
> the new code.  But to avoid mission creep I'd decided not draw
> the line there and avoid trying to fix the problem completely.
> (I've enhanced this in the attached update.)
> 
>>
>> We process the first instance of x1, find it is local and bump count.
>> When we encounter the second instance, it's in our map and we do
>> nothing.  THen we process x2 and bump count again.  So count would be 2.
>>   But count is going to be less than nargs so we'll set *pmaybe to true.
>>
>> ISTM you'd have to record the result for each phi argument and query
>> that to determine if you should bump the counter if you've already
>> visited the argument.
> 
> I suspect that no solution will be perfect, at a minimum because
> multiple return statements sometimes get merged into one, so what
> in the source code is a single return that definitely returns
> the address a local may end up merged with one that returns
> a null (and triggering a maybe kind of warning).  I have also
> seen warnings in some non-trivial test cases that suggest that
> some return statements get split up into two where a "may return"
> could turn into a "definitely does return."
> 
>> It also seems to me that you can break the loop as soon as you've got a
>> nonzero count and get a false return from is_addr_local.   Not sure if
>> that'll matter in practice or not.
> 
> This is only possible if we're willing to lose some detail (i.e.,
> if we are willing to only point to one variable when returning
> the address of two or more).  I don't find that very user-friendly.
> 
> To wrap up, while I would have preferred taking a simpler approach
> at first and making bigger design changes later, I have redesigned
> the warning for better accuracy as you suggested above.
> 
> The attached revised patch first collects the return statements
> in a hash table along with the locations of the local variables
> whose addresses each statement returns, and then issues just one
> warning for each statement, listing all the locals it refers to
> in subsequent notes.
> 
> In addition, since it was nearly trivial, I also added checking
> for returning addresses of locals via calls to built-ins like
> memcpy.

I didn't fully retest the patch after a minor tweak and sure enough,
the tweak was wrong.  Please apply the following on top of the patch.
(We only want to consider the argument of the call.  The LHS is
the call itself.)

diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
index eb8e754870f..bb9616f26ec 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -475,14 +475,9 @@ is_addr_local (gimple *return_stmt, tree exp, 
location_t *ploc,
             case BUILT_IN_STRNCAT_CHK:
             case BUILT_IN_STRNCPY:
             case BUILT_IN_STRNCPY_CHK:
-             /* Check both the argument and the return value.  */
-             return (is_addr_local (return_stmt,
-                                    gimple_call_arg (def_stmt, 0),
-                                    ploc, plocmap, visited)
-                     || is_addr_local (return_stmt,
-                                       gimple_call_lhs (def_stmt),
-                                       ploc, plocmap, visited));
-
+             return is_addr_local (return_stmt,
+                                   gimple_call_arg (def_stmt, 0),
+                                   ploc, plocmap, visited);
             default:
               return false;
             }
> 
> I have beefed up the test suite to exercise non-trivial functions
> involving different kinds of expressions and statements, including
> loops.  Except for one xfail due to missing location information
> (bug 90735 - missing location in -Wreturn-local-addr on a function
> with two return statements​) I haven't found any issues.
> The improved warning does find many more problems than the current
> solution.
> 
>> The other approach here (and I'm not suggesting you implement it) would
>> be to use the propagation engine.  That's probably overkill here since
>> we'd probably end up computing a whole lot of things we don't need.  My
>> sense is an on-demand approach like you've done is probably less
>> computationally expensive.
> 
> I'm certainly not opposed to making further improvements (as I
> mentioned, I'd like to add checking for returning freed pointers
> and freeing locals as you suggested).  But I would prefer to make
> these in incremental steps rather than growing what was at first
> meant to be just small bug fix/enhancement for alloca and VLAs.
> 
> The attached update has been tested on x86_64-linux.
> 
> Martin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
       [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
  0 siblings, 2 replies; 23+ messages in thread
From: Martin Sebor @ 2019-06-19  3:19 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

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

On 6/14/19 2:59 PM, Jeff Law wrote:
> On 6/4/19 1:40 PM, Martin Sebor wrote:
>> On 6/3/19 5:24 PM, Martin Sebor wrote:
>>> On 5/31/19 2:46 PM, Jeff Law wrote:
>>>> On 5/22/19 3:34 PM, Martin Sebor wrote:
>>>>> -Wreturn-local-addr detects a subset of instances of returning
>>>>> the address of a local object from a function but the warning
>>>>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>>>>> of ordinary automatic variables[1].
>>>>>
>>>>> The attached patch extends the implementation of the warning to
>>>>> detect those.  It still doesn't detect instances where the address
>>>>> is the result of a built-in such strcpy[2].
>>>>>
>>>>> Tested on x86_64-linux.
>>>>>
>>>>> Martin
>>>>>
>>>>> [1] For example, this is only diagnosed with the patch:
>>>>>
>>>>>     void* f (int i)
>>>>>     {
>>>>>       struct S { int a[2]; } s[2];
>>>>>       return &s->a[i];
>>>>>     }
>>>>>
>>>>> [2] The following is not diagnosed even with the patch:
>>>>>
>>>>>     void sink (void*);
>>>>>
>>>>>     void* f (int i)
>>>>>     {
>>>>>       char a[6];
>>>>>       char *p = __builtin_strcpy (a, "123");
>>>>>       sink (p);
>>>>>       return p;
>>>>>     }
>>>>>
>>>>> I would expect detecting to be possible and useful.  Maybe as
>>>>> a follow-up.
>>>>>
>>>>> 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 c/71924
>>>>>      * gimple-ssa-isolate-paths.c (is_addr_local): New function.
>>>>>      (warn_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.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>>      PR c/71924
>>>>>      * gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>>>>> --- a/gcc/gimple-ssa-isolate-paths.c
>>>>> +++ b/gcc/gimple-ssa-isolate-paths.c
>>>>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple
>>>>> *stmt)
>>>>>      return false;
>>>>>    }
>>>>> +/* Return true if EXPR is a expression of pointer type that refers
>>>>> +   to the address of a variable with automatic storage duration.
>>>>> +   If so, set *PLOC to the location of the object or the call that
>>>>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>>>>> +   also consider PHI statements and set *PMAYBE when some but not
>>>>> +   all arguments of such statements refer to local variables, and
>>>>> +   to clear it otherwise.  */
>>>>> +
>>>>> +static bool
>>>>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>>>>> +           hash_set<gphi *> *visited = NULL)
>>>>> +{
>>>>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>>>>> +          return is_addr_local (ptr, ploc, pmaybe, visited);
>>>>> +        }
>>>>> +      return false;
>>>>> +    }
>>>> So this is going to recurse on the rhs1 of something like
>>>> POINTER_PLUS_EXPR, that's a good thing :-)   But isn't it non-selective
>>>> about the codes where we recurse?
>>>>
>>>> Consider
>>>>
>>>>     ptr = (cond) ? res1 : res2
>>>>
>>>> I think we'll end up recursing on the condition rather than looking at
>>>> res1 and res2.
>>>>
>>>>
>>>> I suspect there are a very limited number of expression codes that
>>>> appear on the RHS where we'd want to recurse on one or both operands.
>>>>
>>>> POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to recurse
>>>> on both and logically and the result), BIT_AND (maybe we masked off some
>>>> bits in an address).  That's probably about it :-)
>>>>
>>>> Are there any other codes you've seen or think would be useful in
>>>> practice to recurse through?  I'd rather list them explicitly rather
>>>> than just recurse down through every rhs1 we encounter.
>>>
>>> I don't have a list of codes to test for.  I initially contemplated
>>> enumerating them but in the end decided the pointer type check would
>>> be sufficient.  I wouldn't expect a COND_EXPR here.  Don't they get
>>> transformed into PHIs?  In all my tests they do and and running
>>> the whole test suite with an assert that it doesn't come up doesn't
>>> expose any either.  (I left the assert for COND_EXPR there.)  If
>>> a COND_EXPR really can come up in a GIMPLE assignment here can you
>>> please show me how so I can add a test for it?
> 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

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:

   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.

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.

> You'll see them for stuff like this:
> 
> 
> ;;   basic block 24, loop depth 0
> ;;    pred:       23
>    _244 = _1 == 0 ? 1 : 2;
> 
> In a few spots.
> 
> 
> In addition to COND_EXPR, I'd be worried about VEC_COND_EXPR,
> VEC_PERM_EXPR and a host of others.
> 
> 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)).

> I'd be much more comfortable with a tight predicate controlling recursion.

I've added ADDR_EXPR, COND_EXPR, MAX_EXPR, MIN_EXPR, NOP_EXPR, and
POINTER_PLUS_EXPR based on the survey I did.  I don't have tests for
the rest so I've left them out for now.  If/when I come up with them
I'll add them.

>>>>> +
>>>>> +      if (code == GIMPLE_PHI && pmaybe)
>>>>> +    {
>>>>> +      unsigned count = 0;
>>>>> +      gphi *phi_stmt = as_a <gphi *> (def_stmt);
>>>>> +
>>>>> +      unsigned nargs = gimple_phi_num_args (phi_stmt);
>>>>> +      for (unsigned i = 0; i < nargs; ++i)
>>>>> +        {
>>>>> +          if (!visited->add (phi_stmt))
>>>>> +        {
>>>>> +          tree arg = gimple_phi_arg_def (phi_stmt, i);
>>>>> +          if (is_addr_local (arg, ploc, pmaybe, visited))
>>>>> +            ++count;
>>>>> +        }
>>>>> +        }
>>>> So imagine
>>>>
>>>> p = phi (x1, x1, x2)
>>>>
>>>> Where both x1 and x2 would pass is_addr_local.  I think this code would
>>>> incorrectly set pmaybe.
>>>
>>> I suppose it would but this happens readily with or without my
>>> patch, for example here:
> 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.

Anyway, attached is an updated revision with the recursion limited
to just the codes you mentioned, and hopefully with an acceptable
accuracy.  The patch depends on a fix for the hash_map bug 90923
that I just posted:
   https://gcc.gnu.org/ml/gcc-patches/2019-06/msg01105.html

Martin

> 
>>>
>>>     int* f (int i)
>>>     {
>>>       int a[2], b[2];
>>>       int *p = i ? a : b;
>>>       return p;
>>>     }
>>>
>>> We end up with two instances of "function may return address
>>> of local variable," one for each local, because
>>> find_implicit_erroneous_behavior only issues the "may return"
>>> kind of warning.
> This looks more like a failing of the core approach in the erroneous
> path code.
> 
>>>
>>> I suspect that no solution will be perfect, at a minimum because
>>> multiple return statements sometimes get merged into one, so what
>>> in the source code is a single return that definitely returns
>>> the address a local may end up merged with one that returns
>>> a null (and triggering a maybe kind of warning).  I have also
>>> seen warnings in some non-trivial test cases that suggest that
>>> some return statements get split up into two where a "may return"
>>> could turn into a "definitely does return."
> Certainly.  I don't expect any solution will be perfect here, but I
> think fixing the logic error in the noted loop would help
> 
>>>
>>>> It also seems to me that you can break the loop as soon as you've got a
>>>> nonzero count and get a false return from is_addr_local.   Not sure if
>>>> that'll matter in practice or not.
>>>
>>> This is only possible if we're willing to lose some detail (i.e.,
>>> if we are willing to only point to one variable when returning
>>> the address of two or more).  I don't find that very user-friendly.
> ACK.  Ignore that comment then.
> 
> 
> Jeff
> 


[-- Attachment #2: gcc-71924.diff --]
[-- Type: text/x-patch, Size: 52967 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.
	(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;
+};
+
+/* 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 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;
+	}
+
+      if (code == GIMPLE_CALL
+	  && gimple_call_builtin_p (def_stmt))
+	{
+	  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;
+
+	  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);
+	  if (argsloc.nargs < nargs)
+	    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.
+   Call isolate_path for each returned address.
+   Return DUPLICATE on success and NULL when no such address was found.  */
+
+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)
+{
+  hash_set<gphi *> visited_phis;
+  /* 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.  */
+  if (!is_addr_local ((gimple*)-1, arg, &locmap, &visited_phis))
+    {
+      /* Remove the placeholder regardless of success or failure.  */
+      locmap.remove ((gimple*)-1);
+      return NULL;
+    }
+
+  const args_loc_t* const placeargsloc = locmap.get ((gimple*)-1);
+  const unsigned nlocs = placeargsloc->locvec.length ();
+  gcc_assert (nlocs);
+
+  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 obtaine 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 ((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.  */
+	  cfg_altered = 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 +629,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,6 +667,12 @@ 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.
 
@@ -395,63 +680,24 @@ find_implicit_erroneous_behavior (void)
 	     index, 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;
 
-	      next_i = i + 1;
+	      duplicate = handle_return_addr_local_phi_arg (bb, duplicate, lhs,
+							    arg, e, locmap,
+							    nargs);
+	      next_i = duplicate ? i : i + 1;
 
-	      if (TREE_CODE (op) == ADDR_EXPR)
-		{
-		  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;
-			    }
-			}
-		    }
-		}
-
-	      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 +726,49 @@ 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);
+
+  /* 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 +814,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/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..94707c3d008
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c
@@ -0,0 +1,49 @@
+/* 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" } */
+}
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/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__
 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PING] [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-19  3:19         ` Martin Sebor
@ 2019-06-26 14:59           ` Martin Sebor
  2019-06-27  0:12           ` Jeff Law
  1 sibling, 0 replies; 23+ messages in thread
From: Martin Sebor @ 2019-06-26 14:59 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

Ping: did my reply and updated patch resolve your concerns?
   https://gcc.gnu.org/ml/gcc-patches/2019-06/msg01106.html

On 6/18/19 9:19 PM, Martin Sebor wrote:
> On 6/14/19 2:59 PM, Jeff Law wrote:
>> On 6/4/19 1:40 PM, Martin Sebor wrote:
>>> On 6/3/19 5:24 PM, Martin Sebor wrote:
>>>> On 5/31/19 2:46 PM, Jeff Law wrote:
>>>>> On 5/22/19 3:34 PM, Martin Sebor wrote:
>>>>>> -Wreturn-local-addr detects a subset of instances of returning
>>>>>> the address of a local object from a function but the warning
>>>>>> doesn't try to handle alloca or VLAs, or some non-trivial cases
>>>>>> of ordinary automatic variables[1].
>>>>>>
>>>>>> The attached patch extends the implementation of the warning to
>>>>>> detect those.  It still doesn't detect instances where the address
>>>>>> is the result of a built-in such strcpy[2].
>>>>>>
>>>>>> Tested on x86_64-linux.
>>>>>>
>>>>>> Martin
>>>>>>
>>>>>> [1] For example, this is only diagnosed with the patch:
>>>>>>
>>>>>>     void* f (int i)
>>>>>>     {
>>>>>>       struct S { int a[2]; } s[2];
>>>>>>       return &s->a[i];
>>>>>>     }
>>>>>>
>>>>>> [2] The following is not diagnosed even with the patch:
>>>>>>
>>>>>>     void sink (void*);
>>>>>>
>>>>>>     void* f (int i)
>>>>>>     {
>>>>>>       char a[6];
>>>>>>       char *p = __builtin_strcpy (a, "123");
>>>>>>       sink (p);
>>>>>>       return p;
>>>>>>     }
>>>>>>
>>>>>> I would expect detecting to be possible and useful.  Maybe as
>>>>>> a follow-up.
>>>>>>
>>>>>> 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 c/71924
>>>>>>      * gimple-ssa-isolate-paths.c (is_addr_local): New function.
>>>>>>      (warn_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.
>>>>>>
>>>>>> gcc/testsuite/ChangeLog:
>>>>>>
>>>>>>      PR c/71924
>>>>>>      * gcc.dg/Wreturn-local-addr-2.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..2933ecf502e 100644
>>>>>> --- a/gcc/gimple-ssa-isolate-paths.c
>>>>>> +++ b/gcc/gimple-ssa-isolate-paths.c
>>>>>> @@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple
>>>>>> *stmt)
>>>>>>      return false;
>>>>>>    }
>>>>>> +/* Return true if EXPR is a expression of pointer type that refers
>>>>>> +   to the address of a variable with automatic storage duration.
>>>>>> +   If so, set *PLOC to the location of the object or the call that
>>>>>> +   allocated it (for alloca and VLAs).  When PMAYBE is non-null,
>>>>>> +   also consider PHI statements and set *PMAYBE when some but not
>>>>>> +   all arguments of such statements refer to local variables, and
>>>>>> +   to clear it otherwise.  */
>>>>>> +
>>>>>> +static bool
>>>>>> +is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
>>>>>> +           hash_set<gphi *> *visited = NULL)
>>>>>> +{
>>>>>> +  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 ptr = gimple_assign_rhs1 (def_stmt);
>>>>>> +          return is_addr_local (ptr, ploc, pmaybe, visited);
>>>>>> +        }
>>>>>> +      return false;
>>>>>> +    }
>>>>> So this is going to recurse on the rhs1 of something like
>>>>> POINTER_PLUS_EXPR, that's a good thing :-)   But isn't it 
>>>>> non-selective
>>>>> about the codes where we recurse?
>>>>>
>>>>> Consider
>>>>>
>>>>>     ptr = (cond) ? res1 : res2
>>>>>
>>>>> I think we'll end up recursing on the condition rather than looking at
>>>>> res1 and res2.
>>>>>
>>>>>
>>>>> I suspect there are a very limited number of expression codes that
>>>>> appear on the RHS where we'd want to recurse on one or both operands.
>>>>>
>>>>> POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to 
>>>>> recurse
>>>>> on both and logically and the result), BIT_AND (maybe we masked off 
>>>>> some
>>>>> bits in an address).  That's probably about it :-)
>>>>>
>>>>> Are there any other codes you've seen or think would be useful in
>>>>> practice to recurse through?  I'd rather list them explicitly rather
>>>>> than just recurse down through every rhs1 we encounter.
>>>>
>>>> I don't have a list of codes to test for.  I initially contemplated
>>>> enumerating them but in the end decided the pointer type check would
>>>> be sufficient.  I wouldn't expect a COND_EXPR here.  Don't they get
>>>> transformed into PHIs?  In all my tests they do and and running
>>>> the whole test suite with an assert that it doesn't come up doesn't
>>>> expose any either.  (I left the assert for COND_EXPR there.)  If
>>>> a COND_EXPR really can come up in a GIMPLE assignment here can you
>>>> please show me how so I can add a test for it?
>> 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
> 
> 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:
> 
>    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.
> 
> 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.
> 
>> You'll see them for stuff like this:
>>
>>
>> ;;   basic block 24, loop depth 0
>> ;;    pred:       23
>>    _244 = _1 == 0 ? 1 : 2;
>>
>> In a few spots.
>>
>>
>> In addition to COND_EXPR, I'd be worried about VEC_COND_EXPR,
>> VEC_PERM_EXPR and a host of others.
>>
>> 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)).
> 
>> I'd be much more comfortable with a tight predicate controlling 
>> recursion.
> 
> I've added ADDR_EXPR, COND_EXPR, MAX_EXPR, MIN_EXPR, NOP_EXPR, and
> POINTER_PLUS_EXPR based on the survey I did.  I don't have tests for
> the rest so I've left them out for now.  If/when I come up with them
> I'll add them.
> 
>>>>>> +
>>>>>> +      if (code == GIMPLE_PHI && pmaybe)
>>>>>> +    {
>>>>>> +      unsigned count = 0;
>>>>>> +      gphi *phi_stmt = as_a <gphi *> (def_stmt);
>>>>>> +
>>>>>> +      unsigned nargs = gimple_phi_num_args (phi_stmt);
>>>>>> +      for (unsigned i = 0; i < nargs; ++i)
>>>>>> +        {
>>>>>> +          if (!visited->add (phi_stmt))
>>>>>> +        {
>>>>>> +          tree arg = gimple_phi_arg_def (phi_stmt, i);
>>>>>> +          if (is_addr_local (arg, ploc, pmaybe, visited))
>>>>>> +            ++count;
>>>>>> +        }
>>>>>> +        }
>>>>> So imagine
>>>>>
>>>>> p = phi (x1, x1, x2)
>>>>>
>>>>> Where both x1 and x2 would pass is_addr_local.  I think this code 
>>>>> would
>>>>> incorrectly set pmaybe.
>>>>
>>>> I suppose it would but this happens readily with or without my
>>>> patch, for example here:
>> 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.
> 
> Anyway, attached is an updated revision with the recursion limited
> to just the codes you mentioned, and hopefully with an acceptable
> accuracy.  The patch depends on a fix for the hash_map bug 90923
> that I just posted:
>    https://gcc.gnu.org/ml/gcc-patches/2019-06/msg01105.html
> 
> Martin
> 
>>
>>>>
>>>>     int* f (int i)
>>>>     {
>>>>       int a[2], b[2];
>>>>       int *p = i ? a : b;
>>>>       return p;
>>>>     }
>>>>
>>>> We end up with two instances of "function may return address
>>>> of local variable," one for each local, because
>>>> find_implicit_erroneous_behavior only issues the "may return"
>>>> kind of warning.
>> This looks more like a failing of the core approach in the erroneous
>> path code.
>>
>>>>
>>>> I suspect that no solution will be perfect, at a minimum because
>>>> multiple return statements sometimes get merged into one, so what
>>>> in the source code is a single return that definitely returns
>>>> the address a local may end up merged with one that returns
>>>> a null (and triggering a maybe kind of warning).  I have also
>>>> seen warnings in some non-trivial test cases that suggest that
>>>> some return statements get split up into two where a "may return"
>>>> could turn into a "definitely does return."
>> Certainly.  I don't expect any solution will be perfect here, but I
>> think fixing the logic error in the noted loop would help
>>
>>>>
>>>>> It also seems to me that you can break the loop as soon as you've 
>>>>> got a
>>>>> nonzero count and get a false return from is_addr_local.   Not sure if
>>>>> that'll matter in practice or not.
>>>>
>>>> This is only possible if we're willing to lose some detail (i.e.,
>>>> if we are willing to only point to one variable when returning
>>>> the address of two or more).  I don't find that very user-friendly.
>> ACK.  Ignore that comment then.
>>
>>
>> Jeff
>>
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  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
  1 sibling, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-06-27  0:12 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

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.

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.


> +
> +/* 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?





Jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-27  0:12           ` Jeff Law
@ 2019-06-30 21:50             ` Martin Sebor
  2019-07-02 20:59               ` Jeff Law
  0 siblings, 1 reply; 23+ messages in thread
From: Martin Sebor @ 2019-06-30 21:50 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

[-- 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__
 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-06-30 21:50             ` Martin Sebor
@ 2019-07-02 20:59               ` Jeff Law
  2019-07-11  6:45                 ` Martin Sebor
  0 siblings, 1 reply; 23+ messages in thread
From: Jeff Law @ 2019-07-02 20:59 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 6/30/19 3:50 PM, Martin Sebor wrote:
> On 6/26/19 6:11 PM, Jeff Law wrote:
[ Another big snip ]
> 
>> 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.
OK.  THanks for explaining why we need the copy assignment operator.


> 
> 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.
You've got my blessing to do that :-)

>>
>> 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.
THanks.

So in the thread for strlen/sprintf the issue around unbounded walks up
through PHI argument's SSA_NAME_DEF_STMT was raised.

I think we've got two options here.

One would be to hold this patch until we sort out what the bounds should
look like, then adjust this patch to do something similar.

Or go forward with this patch, then come back and adjust the walker once
we have settled on solution in the other thread.

I'm OK with either.  Your choice.

jeff

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH] warn on returning alloca and VLA (PR 71924, 90549)
  2019-07-02 20:59               ` Jeff Law
@ 2019-07-11  6:45                 ` Martin Sebor
  0 siblings, 0 replies; 23+ messages in thread
From: Martin Sebor @ 2019-07-11  6:45 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 7/2/19 2:59 PM, Jeff Law wrote:
> On 6/30/19 3:50 PM, Martin Sebor wrote:
>> On 6/26/19 6:11 PM, Jeff Law wrote:
> [ Another big snip ]
>>
>>> 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.
> OK.  THanks for explaining why we need the copy assignment operator.
> 
> 
>>
>> 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.
> You've got my blessing to do that :-)
> 
>>>
>>> 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.
> THanks.
> 
> So in the thread for strlen/sprintf the issue around unbounded walks up
> through PHI argument's SSA_NAME_DEF_STMT was raised.
> 
> I think we've got two options here.
> 
> One would be to hold this patch until we sort out what the bounds should
> look like, then adjust this patch to do something similar.
> 
> Or go forward with this patch, then come back and adjust the walker once
> we have settled on solution in the other thread.
> 
> I'm OK with either.  Your choice.

I committed the patch as is until the question of which algorithms
to put the limit on is answered (as discussed in the thread Re:
sprintf/strlen integration).

Martin

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2019-07-11  2:04 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-22 21:34 [PATCH] warn on returning alloca and VLA (PR 71924, 90549) 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
2019-07-02 20:59               ` Jeff Law
2019-07-11  6:45                 ` Martin Sebor

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).