public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 0/2] provide simple detection of indeterminate pointers
@ 2021-11-01 22:15 Martin Sebor
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
                   ` (2 more replies)
  0 siblings, 3 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-01 22:15 UTC (permalink / raw)
  To: gcc-patches; +Cc: Martin Sebor

This two-patch series adds support for the detection of uses
of pointers invalidated as a result of the lifetime of
the objects they point to having ended: either explicitly,
after a call to a dynamic deallocation function, or implicitly,
by virtue of an object with automatic storage duration having
gone out of scope.

To minimize false positives the initial logic is very simple
(even simplistic): the code only checks uses in basic blocks
dominated by the invalidating calls (either calls to
deallocation functions or GCC's clobbers).

A more thorough checker is certainly possible and I'd say most
desirable but will require a more sophisticated implementation
and a better predicate analyzer than is available, and so will
need to wait for GCC 13.

Martin

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

* [PATCH 1/2] add -Wuse-after-free
  2021-11-01 22:15 [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
@ 2021-11-01 22:17 ` Martin Sebor
  2021-11-02  5:32   ` Eric Gallager
                     ` (2 more replies)
  2021-11-01 22:18 ` [PATCH 2/2] add -Wdangling-pointer [PR #63272] Martin Sebor
  2021-11-08 22:41 ` PING [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
  2 siblings, 3 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-01 22:17 UTC (permalink / raw)
  To: gcc-patches

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

Patch 1 in the series detects a small subset of uses of pointers
made indeterminate by calls to deallocation functions like free
or C++ operator delete.  To control the conditions the warnings
are issued under the new -Wuse-after-free= option provides three
levels.  At the lowest level the warning triggers only for
unconditional uses of freed pointers and doesn't warn for uses
in equality expressions.  Level 2 warns also for come conditional
uses, and level 3 also for uses in equality expressions.

I debated whether to make level 2 or 3 the default included in
-Wall.  I decided on 3 for two reasons: 1) to raise awareness
of both the problem and GCC's new ability to detect it: using
a pointer after it's been freed, even only in principle, by
a successful call to realloc, is undefined, and 2) because
it's trivial to lower the level either globally, or locally
by suppressing the warning around such misuses.

I've tested the patch on x86_64-linux and by building Glibc
and Binutils/GDB.  It triggers a number of times in each, all
due to comparing invalidated pointers for equality (i.e., level
3).  I have suppressed these in GCC (libiberty) by a #pragma,
and will see how the Glibc folks want to deal with theirs (I
track them in BZ #28521).

The tests contain a number of xfails due to limitations I'm
aware of.  I marked them pr?????? until the patch is approved.
I will open bugs for them before committing if I don't resolve
them in a followup.

Martin

[-- Attachment #2: gcc-63272-1.diff --]
[-- Type: text/x-patch, Size: 56808 bytes --]

Add -Wuse-after-free.

gcc/c-family/ChangeLog

	* c.opt (-Wuse-after-free): New options.

gcc/ChangeLog:

	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
	OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
	* diagnostic-spec.h (NW_DANGLING): New enumerator.
	* doc/invoke.texi (-Wuse-after-free): Document new option.
	* gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
	(pass_waccess::check_call_access): ...to this.
	(pass_waccess::check): Rename...
	(pass_waccess::check_block): ...to this.
	(pass_waccess::check_pointer_uses): New function.
	(pass_waccess::gimple_call_return_arg): New function.
	(pass_waccess::warn_invalid_pointer): New function.
	(pass_waccess::check_builtin): Handle free and realloc.
	(gimple_use_after_inval_p): New function.
	(get_realloc_lhs): New function.
	(maybe_warn_mismatched_realloc): New function.
	(pointers_related_p): New function.
	(pass_waccess::check_call): Call check_pointer_uses.
	(pass_waccess::execute): Compute and free dominance info.

libcpp/ChangeLog:

	* files.c (_cpp_find_file): Substitute a valid pointer for
	an invalid one to avoid -Wuse-0after-free.

libiberty/ChangeLog:

	* regex.c: Suppress -Wuse-after-free.

gcc/testsuite/ChangeLog:

	* gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
	* gcc.dg/Wmismatched-dealloc-3.c: Same.
	* gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
	* gcc.dg/attr-alloc_size-7.c: Same.
	* c-c++-common/Wuse-after-free-2.c: New test.
	* c-c++-common/Wuse-after-free-3.c: New test.
	* c-c++-common/Wuse-after-free-4.c: New test.
	* c-c++-common/Wuse-after-free-5.c: New test.
	* c-c++-common/Wuse-after-free-6.c: New test.
	* c-c++-common/Wuse-after-free-7.c: New test.
	* c-c++-common/Wuse-after-free.c: New test.
	* g++.dg/warn/Wdangling-pointer.C: New test.
	* g++.dg/warn/Wmismatched-dealloc-3.C: New test.
	* g++.dg/warn/Wuse-after-free.C: New test.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 06457ac739e..a5fe00ed195 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1334,6 +1334,14 @@ Wunused-const-variable=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_unused_const_variable) Warning LangEnabledBy(C ObjC,Wunused-variable, 1, 0) IntegerRange(0, 2)
 Warn when a const variable is unused.
 
+Wuse-after-free
+C ObjC C++ LTO ObjC++ Alias(Wuse-after-free=, 2, 0) Warning
+Warn for uses of pointers to deallocated strorage.
+
+Wuse-after-free=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_use_after_free) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 3, 0) IntegerRange(0, 3)
+Warn for uses of pointers to deallocated strorage.
+
 Wvariadic-macros
 C ObjC C++ ObjC++ CPP(warn_variadic_macros) CppReason(CPP_W_VARIADIC_MACROS) Var(cpp_warn_variadic_macros) Init(0) Warning LangEnabledBy(C ObjC C++ ObjC++,Wpedantic || Wtraditional)
 Warn about using variadic macros.
diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c
index 85ffb725c02..0d68af4d91e 100644
--- a/gcc/diagnostic-spec.c
+++ b/gcc/diagnostic-spec.c
@@ -99,6 +99,11 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt)
 	m_bits = NW_UNINIT;
       break;
 
+    case OPT_Wreturn_local_addr:
+    case OPT_Wuse_after_free_:
+      m_bits = NW_DANGLING;
+      break;
+
     default:
       /* A catchall group for everything else.  */
       m_bits = NW_OTHER;
diff --git a/gcc/diagnostic-spec.h b/gcc/diagnostic-spec.h
index 9b3aaaa3ce6..164c9fc675f 100644
--- a/gcc/diagnostic-spec.h
+++ b/gcc/diagnostic-spec.h
@@ -41,11 +41,13 @@ public:
      NW_UNINIT = 1 << 3,
      /* Warnings about arithmetic overflow.  */
      NW_VFLOW = 1 << 4,
+     /* Warnings about dangling pointers.  */
+     NW_DANGLING = 1 << 5,
      /* All other unclassified warnings.  */
-     NW_OTHER = 1 << 5,
+     NW_OTHER = 1 << 6,
      /* All groups of warnings.  */
      NW_ALL = (NW_ACCESS | NW_LEXICAL | NW_NONNULL
-	       | NW_UNINIT | NW_VFLOW | NW_OTHER)
+	       | NW_UNINIT | NW_VFLOW | NW_DANGLING | NW_OTHER)
    };
 
   nowarn_spec_t (): m_bits () { }
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c5730228821..eb4ecb56dcc 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -4341,6 +4341,60 @@ annotations.
 Warn about overriding virtual functions that are not marked with the
 @code{override} keyword.
 
+@item -Wuse-after-free
+@itemx -Wuse-after-free=@var{n}
+@opindex Wuse-after-free
+@opindex Wno-use-after-free
+Warn about uses of pointers to dynamically allocated objects that have
+been rendered indeterminate by a call to a deallocation function.
+
+@table @gcctabopt
+@item -Wuse-after-free=1
+At level 1 the warning attempts to diagnose only unconditional uses of
+pointers made indeterminate by a deallocation call.  This includes
+double-@code{free} calls.  Although undefined, uses of indeterminate
+pointers in equality (or inequality) expressions are not diagnosed at
+this level.
+@item -Wuse-after-free=2
+At level 2, in addition to unconditional uses the warning also diagnoses
+conditional uses of pointers made indeterminate by a deallocation call.
+As at level 1, uses in (or inequality) equality expressions are not
+diagnosed.  For example, the second call to @code{free} in the following
+function is diagnosed at this level:
+@smallexample
+struct A @{ int refcount; void *data; @};
+
+void release (struct A *p)
+@{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data);   // warning: p may be used after free
+@}
+@end smallexample
+@item -Wuse-after-free=3
+At level 3, the warning also diagnoses uses of indeterminate pointers in
+equality expressions.  All uses of indeterminate pointers are undefined
+but equality tests sometimes appear after calls to @code{realloc} as
+an attempt to determine whether the call resulted in relocating the object
+to a different address.  They are diagnosed at a separate level to aid
+legacy code gradually transition to safe alternatives.  For example,
+the equality test in the function below is diagnosed at this level:
+@smallexample
+void adjust_pointers (int**, int);
+
+void grow (int **p, int n)
+@{
+  int **q = (int**)realloc (p, n *= 2);
+  if (q == p)
+    return;
+  adjust_pointers ((int**)q, n);
+@}
+@end smallexample
+@end table
+
+@option{-Wuse-after-free=3} is included in @option{-Wall}.
+
 @item -Wuseless-cast @r{(C++ and Objective-C++ only)}
 @opindex Wuseless-cast
 @opindex Wno-useless-cast
@@ -5661,6 +5715,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wuse-after-free=3  @gol
 -Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index 63fc27a1487..2065402a2b9 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -50,6 +50,7 @@
 #include "stringpool.h"
 #include "attribs.h"
 #include "demangle.h"
+#include "attr-fnspec.h"
 #include "pointer-query.h"
 
 /* Return true if tree node X has an associated location.  */
@@ -2068,6 +2069,7 @@ class pass_waccess : public gimple_opt_pass
   opt_pass *clone () { return new pass_waccess (m_ctxt); }
 
   virtual bool gate (function *);
+
   virtual unsigned int execute (function *);
 
 private:
@@ -2081,14 +2083,14 @@ private:
   /* Check a call to a built-in function.  */
   bool check_builtin (gcall *);
 
-  /* Check a call to an ordinary function.  */
-  bool check_call (gcall *);
+  /* Check a call to an ordinary function for invalid accesses.  */
+  bool check_call_access (gcall *);
 
   /* Check statements in a basic block.  */
-  void check (basic_block);
+  void check_block (basic_block);
 
   /* Check a call to a function.  */
-  void check (gcall *);
+  void check_call (gcall *);
 
   /* Check a call to the named built-in function.  */
   void check_alloca (gcall *);
@@ -2104,7 +2106,15 @@ private:
   void maybe_check_dealloc_call (gcall *);
   void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *);
 
-  /* A pointer_query object and its cache to store information about
+  /* Check for uses of indeterminate pointers.  */
+  void check_pointer_uses (gimple *, tree);
+
+  /* Return the argument that a call returns.  */
+  tree gimple_call_return_arg (gcall *);
+
+  void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
+
+/* A pointer_query object and its cache to store information about
      pointers and their targets in.  */
   pointer_query m_ptr_qry;
   pointer_query::cache_type m_var_cache;
@@ -2793,6 +2803,15 @@ pass_waccess::check_builtin (gcall *stmt)
       check_read_access (stmt, call_arg (stmt, 0));
       return true;
 
+    case BUILT_IN_FREE:
+    case BUILT_IN_REALLOC:
+      {
+	tree arg = call_arg (stmt, 0);
+	if (TREE_CODE (arg) == SSA_NAME)
+	  check_pointer_uses (stmt, arg);
+      }
+      return true;
+
     case BUILT_IN_GETTEXT:
     case BUILT_IN_PUTS:
     case BUILT_IN_PUTS_UNLOCKED:
@@ -2897,6 +2916,7 @@ pass_waccess::check_builtin (gcall *stmt)
 	return true;
       break;
     }
+
   return false;
 }
 
@@ -3209,7 +3229,7 @@ pass_waccess::maybe_check_access_sizes (rdwr_map *rwm, tree fndecl, tree fntype,
    accesses.  Return true if a call has been handled.  */
 
 bool
-pass_waccess::check_call (gcall *stmt)
+pass_waccess::check_call_access (gcall *stmt)
 {
   tree fntype = gimple_call_fntype (stmt);
   if (!fntype)
@@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call (gcall *call)
     }
 }
 
+/* Return true if either USE_STMT's basic block (that of a pointer's use)
+   is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
+   which is either a clobber or a deallocation call), or if they're in
+   the same block, USE_STMT follows INVAL_STMT.  */
+
+static bool
+gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
+			  bool last_block = false)
+{
+  tree clobvar =
+    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE;
+
+  basic_block inval_bb = gimple_bb (inval_stmt);
+  basic_block use_bb = gimple_bb (use_stmt);
+
+  if (inval_bb != use_bb)
+    {
+      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
+	return true;
+
+      if (!clobvar || !last_block)
+	return false;
+
+      auto gsi = gsi_for_stmt (use_stmt);
+
+      auto_bitmap visited;
+
+      /* A use statement in the last basic block in a function or one that
+	 falls through to it is after any other prior clobber of the used
+	 variable unless it's followed by a clobber of the same variable. */
+      basic_block bb = use_bb;
+      while (bb != inval_bb
+	     && single_succ_p (bb)
+	     && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
+	{
+	  if (!bitmap_set_bit (visited, bb->index))
+	    /* Avoid cycles. */
+	    return true;
+
+	  for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gimple_clobber_p (stmt))
+		{
+		  if (clobvar == gimple_assign_lhs (stmt))
+		    /* The use is followed by a clobber.  */
+		    return false;
+		}
+	    }
+
+	  bb = single_succ (bb);
+	  gsi = gsi_start_bb (bb);
+	}
+
+      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
+    }
+
+  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
+       gsi_next_nondebug (&si))
+    {
+      gimple *stmt = gsi_stmt (si);
+      if (stmt == use_stmt)
+	return true;
+    }
+
+  return false;
+}
+
+/* Issue a warning for the USE_STMT of pointer PTR rendered invalid
+   by INVAL_STMT.  PTR may be null when it's been optimized away.
+   MAYBE is true to issue the "maybe" kind of warning.  EQUALITY is
+   true when the pointer is used in an equality expression.  */
+
+void
+pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
+				    gimple *inval_stmt,
+				    bool maybe,
+				    bool equality /* = false */)
+{
+  /* Avoid printing the unhelpful "<unknown>" in the diagnostics.  */
+  if (ptr && TREE_CODE (ptr) == SSA_NAME
+      && (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr))))
+    ptr = NULL_TREE;
+
+  location_t use_loc = gimple_location (use_stmt);
+  if (use_loc == UNKNOWN_LOCATION)
+    {
+      use_loc = cfun->function_end_locus;
+      if (!ptr)
+	/* Avoid issuing a warning with no context other than
+	   the function.  That would make it difficult to debug
+	   in any but very simple cases.  */
+	return;
+    }
+
+  if (is_gimple_call (inval_stmt))
+    {
+      if ((equality && warn_use_after_free < 3)
+	  || (maybe && warn_use_after_free < 2)
+	  || warning_suppressed_p (use_stmt, OPT_Wuse_after_free_))
+	return;
+
+      const tree inval_decl = gimple_call_fndecl (inval_stmt);
+
+      if ((ptr && warning_at (use_loc, OPT_Wuse_after_free_,
+			      (maybe
+			       ? G_("pointer %qE may be used after %qD")
+			       : G_("pointer %qE used after %qD")),
+			      ptr, inval_decl))
+	  || (!ptr && warning_at (use_loc, OPT_Wuse_after_free_,
+			      (maybe
+			       ? G_("pointer may be used after %qD")
+			       : G_("pointer used after %qD")),
+				  inval_decl)))
+	{
+	  location_t loc = gimple_location (inval_stmt);
+	  inform (loc, "call to %qD here", inval_decl);
+	  suppress_warning (use_stmt, OPT_Wuse_after_free_);
+	}
+      return;
+    }
+}
+
+/* If STMT is a call to either the standard realloc or to a user-defined
+   reallocation function returns its LHS and set *PTR to the reallocated
+   pointer.  Otherwise return null.  */
+
+static tree
+get_realloc_lhs (gimple *stmt, tree *ptr)
+{
+  if (gimple_call_builtin_p (stmt, BUILT_IN_REALLOC))
+    {
+      *ptr = gimple_call_arg (stmt, 0);
+      return gimple_call_lhs (stmt);
+    }
+
+  gcall *call = dyn_cast<gcall *>(stmt);
+  if (!call)
+    return NULL_TREE;
+
+  tree fnattr = NULL_TREE;
+  tree fndecl = gimple_call_fndecl (call);
+  if (fndecl)
+    fnattr = DECL_ATTRIBUTES (fndecl);
+  else
+    {
+      tree fntype = gimple_call_fntype (stmt);
+      if (!fntype)
+	return NULL_TREE;
+      fnattr = TYPE_ATTRIBUTES (fntype);
+    }
+
+  if (!fnattr)
+    return NULL_TREE;
+
+  for (tree ats = fnattr;  (ats = lookup_attribute ("*dealloc", ats));
+       ats = TREE_CHAIN (ats))
+    {
+      tree args = TREE_VALUE (ats);
+      if (!args)
+	continue;
+
+      tree alloc = TREE_VALUE (args);
+      if (!alloc)
+	continue;
+
+      if (alloc == DECL_NAME (fndecl))
+	{
+	  unsigned argno = 0;
+	  if (tree index = TREE_CHAIN (args))
+	    argno = TREE_INT_CST_LOW (TREE_VALUE (index)) - 1;
+	  *ptr = gimple_call_arg (stmt, argno);
+	  return gimple_call_lhs (stmt);
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* Warn if STMT is a call to a deallocation function that's not a match
+   for the REALLOC_STMT call.  Return true if warned.  */
+
+static bool
+maybe_warn_mismatched_realloc (tree ptr, gimple *realloc_stmt, gimple *stmt)
+{
+  if (!is_gimple_call (stmt))
+    return false;
+
+  tree fndecl = gimple_call_fndecl (stmt);
+  if (!fndecl)
+    return false;
+
+  unsigned argno = fndecl_dealloc_argno (fndecl);
+  if (call_nargs (stmt) <= argno)
+    return false;
+
+  if (matching_alloc_calls_p (realloc_stmt, fndecl))
+    return false;
+
+  /* Avoid printing the unhelpful "<unknown>" in the diagnostics.  */
+  if (ptr && TREE_CODE (ptr) == SSA_NAME
+      && (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr))))
+    ptr = NULL_TREE;
+
+  location_t loc = gimple_location (stmt);
+  tree realloc_decl = gimple_call_fndecl (realloc_stmt);
+  tree dealloc_decl = gimple_call_fndecl (stmt);
+  if (ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
+			  "%qD called on pointer %qE passed to mismatched "
+			  "allocation function %qD",
+			  dealloc_decl, ptr, realloc_decl))
+    return false;
+  if (!ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
+			   "%qD called on a pointer passed to mismatched "
+			   "reallocation function %qD",
+			   dealloc_decl, realloc_decl))
+    return false;
+
+  inform (gimple_location (realloc_stmt),
+	  "call to %qD", realloc_decl);
+  return true;
+}
+
+/* Return true if P and Q point to the same object, and false if they
+   either don't or their relationship cannot be determined.  */
+
+static bool
+pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
+{
+  if (!ptr_derefs_may_alias_p (p, q))
+    return false;
+
+  /* TODO: Work harder to rule out relatedness.  */
+  access_ref pref, qref;
+  if (!qry.get_ref (p, stmt, &pref, 0)
+      || !qry.get_ref (q, stmt, &qref, 0))
+    return true;
+
+  return pref.ref == qref.ref;
+}
+
+/* For a STMT either a call to a deallocation function or a clobber, warn
+   for uses of the pointer PTR it was called with (including its copies
+   or others derived from it by pointer arithmetic).  */
+
+void
+pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
+{
+  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
+
+  const bool check_dangling = !is_gimple_call (stmt);
+  basic_block stmt_bb = gimple_bb (stmt);
+
+  /* If the deallocation (or clobber) statement dominates more than
+     a single basic block issue a "maybe" kind of warning.  */
+  bool maybe = !single_succ_p (stmt_bb);
+
+  /* If STMT is a reallocation function set to the reallocated pointer
+     and the LHS of the call, respectively.  */
+  tree realloc_ptr = NULL_TREE;
+  tree realloc_lhs = get_realloc_lhs (stmt, &realloc_ptr);
+
+  auto_bitmap visited;
+
+  auto_vec<tree> pointers;
+  pointers.safe_push (ptr);
+
+  /* Starting with PTR, iterate over POINTERS added by the loop, and
+     either warn for their uses in basic blocks dominated by the STMT
+     or in statements that follow it in the same basic block, or add
+     them to POINTERS if they point into the same object as PTR (i.e.,
+     are obtained by pointer arithmetic on PTR).  */
+  for (unsigned i = 0; i != pointers.length (); ++i)
+    {
+      tree ptr = pointers[i];
+      if (TREE_CODE (ptr) == SSA_NAME
+	  && !bitmap_set_bit (visited, SSA_NAME_VERSION (ptr)))
+	/* Avoid revisiting the same pointer.  */
+	continue;
+
+      use_operand_p use_p;
+      imm_use_iterator iter;
+      FOR_EACH_IMM_USE_FAST (use_p, iter, ptr)
+	{
+	  gimple *use_stmt = USE_STMT (use_p);
+	  if (use_stmt == stmt || is_gimple_debug (use_stmt))
+	    continue;
+
+	  if (realloc_lhs)
+	    {
+	      /* Check to see if USE_STMT is a mismatched deallocation
+		 call for the pointer passed to realloc.  That's a bug
+		 regardless of the pointer's value and so warn.  */
+	      if (maybe_warn_mismatched_realloc (*use_p->use, stmt, use_stmt))
+		continue;
+
+	      /* Pointers passed to realloc that are used in basic blocks
+		 where the realloc call is known to have failed are valid.
+		 Ignore pointers that nothing is known about.  Those could
+		 have escaped along with their nullness.  */
+	      value_range vr;
+	      if (m_ptr_qry.rvals->range_of_expr (vr, realloc_lhs, use_stmt))
+		{
+		  if (vr.zero_p ())
+		    continue;
+
+		  if (!pointers_related_p (stmt, ptr, realloc_ptr, m_ptr_qry))
+		    continue;
+		}
+	    }
+
+	  if (check_dangling
+	      && gimple_code (use_stmt) == GIMPLE_RETURN
+	      && optimize && flag_isolate_erroneous_paths_dereference)
+	    /* Avoid interfering with -Wreturn-local-addr (which runs only
+	       with optimization enabled).  */
+	    continue;
+
+	  bool equality = false;
+	  if (is_gimple_assign (use_stmt))
+	    {
+	      tree_code code = gimple_assign_rhs_code (use_stmt);
+	      equality = code == EQ_EXPR || code == NE_EXPR;
+	    }
+	  else if (gcond *cond = dyn_cast<gcond *>(use_stmt))
+	    {
+	      tree_code code = gimple_cond_code (cond);
+	      equality = code == EQ_EXPR || code == NE_EXPR;
+	    }
+
+	  /* Warn if USE_STMT is dominated by the deallocation STMT.
+	     Otherwise, add the pointer to POINTERS so that the uses
+	     of any other pointers derived from it can be checked.  */
+	  if (gimple_use_after_inval_p (stmt, use_stmt, check_dangling))
+	    {
+	      /* TODO: Handle PHIs but careful of false positives.  */
+	      if (gimple_code (use_stmt) != GIMPLE_PHI)
+		{
+		  warn_invalid_pointer (*use_p->use, use_stmt, stmt,
+					maybe, equality);
+		  continue;
+		}
+	    }
+
+	  if (is_gimple_assign (use_stmt))
+	    {
+	      tree lhs = gimple_assign_lhs (use_stmt);
+	      if (TREE_CODE (lhs) == SSA_NAME)
+		{
+		  tree_code rhs_code = gimple_assign_rhs_code (use_stmt);
+		  if (rhs_code == POINTER_PLUS_EXPR || rhs_code == SSA_NAME)
+		    pointers.safe_push (lhs);
+		}
+	      continue;
+	    }
+
+	  if (gcall *call = dyn_cast <gcall *>(use_stmt))
+	    {
+	      if (gimple_call_return_arg (call))
+		if (tree lhs = gimple_call_lhs (call))
+		  if (TREE_CODE (lhs) == SSA_NAME)
+		    pointers.safe_push (lhs);
+	      continue;
+	    }
+	}
+    }
+}
+
 /* Check call STMT for invalid accesses.  */
 
 void
-pass_waccess::check (gcall *stmt)
+pass_waccess::check_call (gcall *stmt)
 {
   if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
     check_builtin (stmt);
 
-  if (is_gimple_call (stmt))
-    check_call (stmt);
+  if (tree callee = gimple_call_fndecl (stmt))
+    {
+      /* Check for uses of the pointer passed to either a standard
+	 or a user-defined deallocation function.  */
+      unsigned argno = fndecl_dealloc_argno (callee);
+      if (argno < (unsigned) call_nargs (stmt))
+	{
+	  tree arg = call_arg (stmt, argno);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    check_pointer_uses (stmt, arg);
+	}
+    }
+
+  check_call_access (stmt);
 
   maybe_check_dealloc_call (stmt);
-
   check_nonstring_args (stmt);
 }
 
+
 /* Check basic block BB for invalid accesses.  */
 
 void
-pass_waccess::check (basic_block bb)
+pass_waccess::check_block (basic_block bb)
 {
   /* Iterate over statements, looking for function calls.  */
-  for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
+  for (auto si = gsi_start_bb (bb); !gsi_end_p (si);
+       gsi_next_nondebug (&si))
+    {
+      gimple *stmt = gsi_stmt (si);
+      if (gcall *call = dyn_cast <gcall *> (stmt))
+	check_call (call);
+    }
+}
+
+/* Return the argument that the call STMT to a built-in function returns
+   (including with an offset) or null if it doesn't.  */
+
+tree
+pass_waccess::gimple_call_return_arg (gcall *call)
+{
+  /* Check for attribute fn spec to see if the function returns one
+     of its arguments.  */
+  attr_fnspec fnspec = gimple_call_fnspec (call);
+  unsigned int argno;
+  if (!fnspec.returns_arg (&argno))
     {
-      if (gcall *call = dyn_cast <gcall *> (gsi_stmt (si)))
-	check (call);
+      if (gimple_call_num_args (call) < 1)
+	return NULL_TREE;
+
+      if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL))
+	return NULL_TREE;
+
+      tree fndecl = gimple_call_fndecl (call);
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_MEMPCPY:
+	case BUILT_IN_MEMPCPY_CHK:
+	case BUILT_IN_MEMCHR:
+	case BUILT_IN_STRCHR:
+	case BUILT_IN_STRRCHR:
+	case BUILT_IN_STRSTR:
+	case BUILT_IN_STPCPY:
+	case BUILT_IN_STPCPY_CHK:
+	case BUILT_IN_STPNCPY:
+	case BUILT_IN_STPNCPY_CHK:
+	  argno = 0;
+	  break;
+
+	default:
+	  return NULL_TREE;
+	}
     }
+
+  if (gimple_call_num_args (call) <= argno)
+    return NULL_TREE;
+
+  return gimple_call_arg (call, argno);
 }
 
 /* Check function FUN for invalid accesses.  */
@@ -3431,12 +3878,14 @@ pass_waccess::check (basic_block bb)
 unsigned
 pass_waccess::execute (function *fun)
 {
+  calculate_dominance_info (CDI_DOMINATORS);
+
   /* Create a new ranger instance and associate it with FUN.  */
   m_ptr_qry.rvals = enable_ranger (fun);
 
   basic_block bb;
   FOR_EACH_BB_FN (bb, fun)
-    check (bb);
+    check_block (bb);
 
   if (dump_file)
     m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
@@ -3448,6 +3897,7 @@ pass_waccess::execute (function *fun)
   disable_ranger (fun);
   m_ptr_qry.rvals = NULL;
 
+  free_dominance_info (CDI_DOMINATORS);
   return 0;
 }
 
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-2.c b/gcc/testsuite/c-c++-common/Wuse-after-free-2.c
new file mode 100644
index 00000000000..81dd0b7f41d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-2.c
@@ -0,0 +1,137 @@
+/* Verify that accessing freed objects by built-in functions is diagnosed.
+   { dg-do compile }
+   { dg-options "-Wall" }  */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+EXTERN_C void* realloc (void*, size_t);
+
+EXTERN_C void* memcpy (void*, const void*, size_t);
+EXTERN_C char* strcpy (char*, const char*);
+EXTERN_C size_t strlen (const char*);
+
+
+void sink (void*, ...);
+
+struct Member { char *p; char a[4]; };
+
+int nowarn_strcpy_memptr (struct Member *p)
+{
+  char *q = strcpy (p->p, p->a);
+  free (p);
+  return *q;
+}
+
+int nowarn_strlen_memptr (struct Member *p)
+{
+  const char *q = p->p;
+
+  free (p);
+
+  return strlen (q);
+}
+
+int warn_strlen_memptr (struct Member *p)
+{
+  free (p);                   // { dg-message "call to 'free'" "note" }
+  return strlen (p->p);       // { dg-warning "-Wuse-after-free" }
+}
+
+int warn_strlen_memarray (struct Member *p)
+{
+  {
+    free (p);
+    return strlen (p->a);     // { dg-warning "-Wuse-after-free" }
+  }
+
+  {
+    char *q = p->a;
+
+    free (p);
+    return strlen (q);        // { dg-warning "-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void* nowarn_realloc_success (void *p)
+{
+  void *q = realloc (p, 7);
+  if (!q)
+    /* When realloc fails the original pointer remains valid.  */
+    return p;
+
+  return q;
+}
+
+void* warn_realloc_unchecked (void *p, int *moved)
+{
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  *moved = p != q;            // { dg-warning "-Wuse-after-free" }
+  return q;
+}
+
+void* nowarn_realloc_unchecked_copy (void *p1, void *p2, const void *s,
+				     int n, int *x)
+{
+  void *p3 = memcpy (p1, s, n);
+  void *p4 = realloc (p2, 7);
+  *x = p3 != p4;
+  return p4;
+}
+
+void* warn_realloc_unchecked_copy (void *p, const void *s, int n, int *moved)
+{
+  void *p2 = memcpy (p, s, n);
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  *moved = p2 != q;           // { dg-warning "-Wuse-after-free" }
+  return q;
+}
+
+void* warn_realloc_failed (void *p, int *moved)
+{
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  if (q)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      *moved = p != q;        // { dg-warning "-Wuse-after-free" }
+      return q;
+    }
+
+  return p;
+}
+
+extern void *evp;
+
+void* warn_realloc_extern (void *p, int *moved)
+{
+  evp = realloc (p, 7);
+  if (evp)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      *moved = p != evp;      // { dg-warning "-Wuse-after-free" "escaped" }
+      return evp;
+    }
+
+  return p;                   // { dg-bogus "-Wuse-after-free" "safe use after realloc failure" { xfail *-*-* } }
+}
+
+struct A { void *p, *q; int moved; };
+
+void* warn_realloc_arg (struct A *p)
+{
+  p->q = realloc (p->p, 7);
+  if (p->q)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      p->moved = p->p != p->q;  // { dg-warning "-Wuse-after-free" "escaped" { xfail *-*-* } }
+      return p->q;
+    }
+
+  return p->p;
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-3.c b/gcc/testsuite/c-c++-common/Wuse-after-free-3.c
new file mode 100644
index 00000000000..0a2db1a16c8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-3.c
@@ -0,0 +1,83 @@
+/* Exercise -Wuse-after-free with user-defined deallocators.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+EXTERN_C void free (void *);
+EXTERN_C void* realloc (void *, size_t);
+
+typedef struct List { struct List *next; } List;
+
+// User-defined allocator/deallocator just like like realloc and free.
+extern                     void  list_free (List *);
+extern                     List* list_realloc (size_t, List *);
+extern A (list_realloc, 2) List* list_realloc (size_t, List *);
+extern A (list_free, 1)    List* list_realloc (size_t, List *);
+
+
+void sink (void *);
+
+extern int ei;
+extern List *elp, *elpa[];
+
+void nowarn_list_free (struct List *lp)
+{
+  {
+    list_free (lp);
+    lp = 0;
+    sink (lp);
+  }
+  {
+    list_free (elp);
+    elp = 0;
+    sink (elp);
+  }
+  {
+    list_free (elpa[0]);
+    elpa[0] = 0;
+    sink (elpa[0]);
+  }
+  {
+    void *vp = elpa[0];
+    list_free (elpa[0]);
+    sink (vp);
+  }
+  {
+    List *p = elpa[1];
+    if (ei & 1)
+      list_free (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    list_free (lp);
+    list_free (next);
+  }
+}
+
+
+void nowarn_list_free_list (List *head)
+{
+  for (List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      list_free (p);
+    }
+}
+
+void warn_list_free_list (List *head)
+{
+  List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    list_free (p);            // { dg-message "call to '\(void \)?list_free\(\\(List\\*\\)\)?'" "note" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-4.c b/gcc/testsuite/c-c++-common/Wuse-after-free-4.c
new file mode 100644
index 00000000000..686ba7e256c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-4.c
@@ -0,0 +1,102 @@
+/* Verify -Wuse-after-free=1 triggers only for unconditional uses and
+   not for equality expressions.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=1" } */
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void nowarn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void nowarn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* nowarn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void nowarn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],
+     c ? p <= q[1] : q[1][1],
+     c ? p > q[2] : q[2][2],
+     c ? p >= q[3] : q[3][3],
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify no warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data);   // no warning at level 1
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-5.c b/gcc/testsuite/c-c++-common/Wuse-after-free-5.c
new file mode 100644
index 00000000000..c6ff1f3fad2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-5.c
@@ -0,0 +1,103 @@
+/* Verify -Wuse-after-free=2 triggers for conditional as well as
+   unconditional uses but not for equality expressions.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=2" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-6.c b/gcc/testsuite/c-c++-common/Wuse-after-free-6.c
new file mode 100644
index 00000000000..581b1a0a024
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-6.c
@@ -0,0 +1,105 @@
+/* Verify -Wuse-after-free=2 triggers for conditional as well as
+   unconditional uses but not for equality expressions.  Same as
+   -Wuse-after-free-5.c but with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wuse-after-free=2" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  // PHI handling not fully implemented.
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" "pr??????" { xfail *-*-* } }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-7.c b/gcc/testsuite/c-c++-common/Wuse-after-free-7.c
new file mode 100644
index 00000000000..12bb6f24ea5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-7.c
@@ -0,0 +1,103 @@
+/* Verify -Wuse-after-free=3 triggers for conditional and unconditional
+   uses in all expressions including equality.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=3" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],     // { dg-warning "pointer 'p' used" }
+     p != q[5]      // { dg-warning "pointer 'p' used" }
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4], // { dg-warning "pointer 'p' may be used" }
+     c ? p != q[5] : q[5][5], // { dg-warning "pointer 'p' may be used" }
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free.c b/gcc/testsuite/c-c++-common/Wuse-after-free.c
new file mode 100644
index 00000000000..81bb7ff3841
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free.c
@@ -0,0 +1,164 @@
+/* Exercise basic cases of -Wuse-after-free without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* alloca (size_t);
+
+EXTERN_C void* calloc (size_t, size_t);
+EXTERN_C void* malloc (size_t);
+
+EXTERN_C void free (void*);
+
+
+void sink (void *);
+
+extern void* evp;
+extern void* evpa[];
+
+extern int ei;
+
+struct List { struct List *next; };
+
+void nowarn_free (void *vp, struct List *lp)
+{
+  {
+    free (vp);
+    vp = 0;
+    sink (vp);
+  }
+  {
+    free (evp);
+    evp = 0;
+    sink (evp);
+  }
+  {
+    free (evpa[0]);
+    evpa[0] = 0;
+    sink (evpa[0]);
+  }
+  {
+    void *vp = evpa[0];
+    free (evpa[1]);
+    sink (vp);
+  }
+  {
+    void *p = evpa[1];
+    if (ei & 1)
+      free (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    free (lp);
+    free (next);
+  }
+}
+
+void nowarn_free_arg (void *p, void *q)
+{
+  free (p);
+  if (q)
+    free (q);
+}
+
+void nowarn_free_extern (void)
+{
+  extern void *ep, *eq;
+  free (ep);
+  ep = eq;
+  free (ep);
+}
+
+void nowarn_free_assign (void)
+{
+  extern void *ep;
+  free (ep);
+  ep = 0;
+  free (ep);
+}
+
+#pragma GCC diagnostic push
+/* Verify that -Wuse-after-free works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wuse-after-free"
+
+void nowarn_double_free_suppressed (void *p)
+{
+  free (p);
+  free (p);
+}
+
+#pragma GCC diagnostic pop
+
+void warn_double_free_arg (void *p)
+{
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  // Verify exactly one warning is issued.
+  free (p);                   // { dg-warning "\\\-Wuse-after-free" }
+                              // { dg-bogus "\\\-Wuse-after-free" "duplicate warning" { target *-*-* } .-1 }
+
+}
+
+void warn_double_free_extern (void)
+{
+  /* GCC assumes free() clobbers global memory and the warning is
+     too simplistic to see through that assumption.  */
+  extern void *ep, *eq;
+  {
+    eq = ep;
+    free (ep);                // { dg-message "call to 'free'" "pr??????" { xfail *-*-* } }
+    free (eq);                // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void warn_deref_after_free (int *p, int i)
+{
+  int *q0 = p, *q1 = p + 1, *qi = p + i;
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  *p = 0;                     // { dg-warning "\\\-Wuse-after-free" }
+
+  *q0 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *q1 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *qi = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_array_ref_after_free (int *p, int i)
+{
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  p[i] = 0;                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void nowarn_free_list (struct List *head)
+{
+  for (struct List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      free (p);
+    }
+}
+
+void warn_free_list (struct List *head)
+{
+  struct List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    free (p);                 // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+}
+
+
+void warn_free (void *vp)
+{
+  {
+    free (vp);                // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+    evp = vp;                 // { dg-warning "-Wuse-after-free" }
+    evpa[0] = vp;             // { dg-warning "-Wuse-after-free" }
+    evpa[1] = evp;
+  }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
new file mode 100644
index 00000000000..54950bfb0e2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
@@ -0,0 +1,34 @@
+/* { dg-do compile }
+   { dg-options "-Wall -Wno-class-memaccess" } */
+
+extern "C" void* memset (void*, int, __SIZE_TYPE__);
+
+void sink (void*);
+
+struct S { S (); };
+
+void nowarn_array_access ()
+{
+  /* Verify that the clobber in the exceptional basic block doesn't
+     cause bogus warnings.  */
+  S a[1];
+  memset (a, 0, sizeof a);
+  sink (a);
+}
+
+
+void nowarn_array_access_cond (int i)
+{
+  if (i)
+    {
+      S a1[1];
+      memset (a1, 0, sizeof a1);
+      sink (a1);
+    }
+  else
+    {
+      S a2[2];
+      memset (a2, 0, sizeof a2);
+      sink (a2);
+    }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
new file mode 100644
index 00000000000..05c7feef5c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
@@ -0,0 +1,70 @@
+/* Verify that passing a pointer to a deallocation function that was
+   previously passed to a mismatched reallocation function is diagnosed
+   by -Wmismatched-dealloc (and not by some other warning).
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern "C"
+{
+  void free (void *);
+  void* realloc (void *, size_t);
+}
+
+// User-defined allocator/deallocator just like like realloc.
+                   int* int_realloc (size_t, int *);
+A (int_realloc, 2) int* int_realloc (size_t, int *);
+
+
+void sink (void *);
+
+
+void* warn_realloc_op_delete (void *p)
+{
+  void *q = realloc (p, 5);   // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  operator delete (p);        // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
+
+void* warn_realloc_op_delete_cond (void *p)
+{
+  void *q = realloc (p, 5);      // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  if (!q)
+    operator delete (p);         // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
+  return q;
+}
+
+void* warn_realloc_array_delete_char (char *p)
+{
+  char *q;
+  q = (char*)realloc (p, 7);  // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  if (!q)
+    delete[] (p);             // { dg-warning "'void operator delete \\\[]\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
+  return q;
+}
+
+
+int* warn_int_realloc_op_delete (int *p)
+{
+  int *q;
+  q = int_realloc (5, p);     // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
+
+  operator delete (p);        // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
+
+
+int* warn_int_realloc_free (int *p)
+{
+  int *q;
+  q = int_realloc (5, p);    // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
+
+  free (p);                   // { dg-warning "'void free\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wuse-after-free.C b/gcc/testsuite/g++.dg/warn/Wuse-after-free.C
new file mode 100644
index 00000000000..022bd8d39f9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wuse-after-free.C
@@ -0,0 +1,158 @@
+/* Exercise basic C++ only cases of -Wuse-after-free without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern "C" void free (void *);
+extern "C" void* realloc (void *, size_t);
+
+void sink (void *);
+
+extern void* evp;
+extern void* evpa[];
+
+extern int ei;
+
+struct List { struct List *next; };
+
+void nowarn_delete (void *vp, struct List *lp)
+{
+  {
+    operator delete (vp);
+    vp = 0;
+    sink (vp);
+  }
+  {
+    operator delete (evp);
+    evp = 0;
+    sink (evp);
+  }
+  {
+    operator delete (evpa[0]);
+    evpa[0] = 0;
+    sink (evpa[0]);
+  }
+  {
+    void *vp = evpa[0];
+    operator delete (evpa[0]);
+    sink (vp);
+  }
+  {
+    void *p = evpa[1];
+    if (ei & 1)
+      operator delete (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    operator delete (lp);
+    operator delete (next);
+  }
+}
+
+void nowarn_delete_arg (void *p, void *q)
+{
+  operator delete (p);
+  if (q)
+    operator delete (q);
+}
+
+void nowarn_delete_extern (void)
+{
+  extern void *ep, *eq;
+  operator delete (ep);
+  ep = eq;
+  operator delete (ep);
+}
+
+void nowarn_delete_assign (void)
+{
+  extern void *ep;
+  operator delete (ep);
+  ep = 0;
+  operator delete (ep);
+}
+
+void warn_double_delete_arg (void *p)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  operator delete (p);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_delete_free_arg (void *p)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  free (p);                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_free_delete_arg (void *p)
+{
+  free (p);                   // { dg-message "call to 'void free\\(void\\*\\)'" "note" }
+  operator delete (p);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_mismatched_double_delete_arg (void *p, void *q)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  operator delete[] (p);      // { dg-warning "\\\-Wuse-after-free" }
+
+  operator delete[] (q);      // { dg-message "call to 'void operator delete \\\[]\\(void\\*\\)'" "note" }
+  operator delete (q);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_double_delete_extern (void)
+{
+  /* GCC assumes operator delete() clobbers global memory and the warning is
+     too simplistic to see through that assumption.  */
+  extern void *ep, *eq;
+  {
+    eq = ep;
+    operator delete (ep);     // { dg-message "call to 'operator delete'" "pr??????" { xfail *-*-* } }
+    operator delete (eq);     // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void warn_deref_after_delete (int *p, int i)
+{
+  int *q0 = p, *q1 = p + 1, *qi = p + i;
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  *p = 0;                     // { dg-warning "\\\-Wuse-after-free" }
+
+  *q0 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *q1 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *qi = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_array_ref_after_delete (int *p, int i)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  p[i] = 0;                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void nowarn_delete_list (struct List *head)
+{
+  for (struct List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      operator delete (p);
+    }
+}
+
+void warn_delete_list (struct List *head)
+{
+  struct List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    operator delete (p);      // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+}
+
+void warn_delete (void *vp)
+{
+  {
+    operator delete (vp);     // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+    evp = vp;                 // { dg-warning "-Wuse-after-free" }
+    evpa[0] = vp;             // { dg-warning "-Wuse-after-free" }
+    evpa[1] = evp;
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
index 21a5ea7c5da..c303d2f1775 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
@@ -26,6 +26,7 @@ void dealloc (void*);
 A (dealloc) void* alloc (int);
 
 void sink (void*);
+void* source (void);
 
 void test_alloc_A (void)
 {
@@ -107,35 +108,35 @@ void test_realloc_A (void *ptr)
 }
 
 
-void test_realloc (void *ptr)
+void test_realloc (void)
 {
   extern void free (void*);
   extern void* realloc (void*, size_t);
 
   {
-    void *p = realloc (ptr, 1);
+    void *p = realloc (source (), 1);
     p = realloc_A (p, 2);
     __builtin_free (p);
   }
 
   {
-    void *p = realloc (ptr, 2);
+    void *p = realloc (source (), 2);
     p = realloc_A (p, 2);
     free (p);
   }
 
   {
-    void *p = realloc (ptr, 3);
+    void *p = realloc (source (), 3);
     free (p);
   }
 
   {
-    void *p = realloc (ptr, 4);
+    void *p = realloc (source (), 4);
     __builtin_free (p);
   }
 
   {
-    void *p = realloc (ptr, 5);         // { dg-message "returned from 'realloc'" }
+    void *p = realloc (source (), 5);   // { dg-message "returned from 'realloc'" }
     dealloc (p);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
   }
 }
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
index 5afcea39b5e..302900662ce 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
@@ -157,6 +157,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = realloc (p, 1);
     q = reallocarray (q, 2, 3);
     sink (q);
@@ -192,6 +193,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 7, 8);
     q = __builtin_realloc (q, 9);
     sink (q);
@@ -199,6 +201,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 7, 8);
     q = realloc (q, 9);
     sink (q);
@@ -206,6 +209,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 8, 9);
     q = reallocarray (q, 3, 4);
     sink (q);
@@ -213,6 +217,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 9, 10);
     q = reallocarray (q, 3, 4);
     sink (q);
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-6.c b/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
index bf010c53607..e28057f9007 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
@@ -5,7 +5,7 @@
    -Walloc-larger-than=maximum.  */
 /* { dg-do compile } */
 /* { dg-require-effective-target alloca } */
-/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345" } */
+/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
 
 #define MAXOBJSZ  12345
 
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
index 3adde5c2270..6c26935211a 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
@@ -4,7 +4,7 @@
    of the maximum specified by -Walloc-size-larger-than=maximum.  */
 /* { dg-do compile } */
 /* { dg-require-effective-target alloca } */
-/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345" } */
+/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
 
 #define SIZE_MAX   __SIZE_MAX__
 #define MAXOBJSZ   12345
diff --git a/libcpp/files.c b/libcpp/files.c
index c93a03c69ef..64989219ce0 100644
--- a/libcpp/files.c
+++ b/libcpp/files.c
@@ -553,12 +553,11 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
 		  {
 		    /* If *hash_slot is NULL, the above
 		       htab_find_slot_with_hash call just created the
-		       slot, but we aren't going to store there
-		       anything, so need to remove the newly created
-		       entry.  htab_clear_slot requires that it is
-		       non-NULL, so store there some non-NULL pointer,
-		       htab_clear_slot will overwrite it
-		       immediately.  */
+		       slot, but we aren't going to store there anything
+		       of use, so need to remove the newly created entry.
+		       htab_clear_slot requires that it is non-NULL, so
+		       store some non-NULL but valid pointer there,
+		       htab_clear_slot will immediately overwrite it.  */
 		    *hash_slot = file;
 		    htab_clear_slot (pfile->file_hash, hash_slot);
 		  }
@@ -582,7 +581,7 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
 		if (*hash_slot == NULL)
 		  {
 		    /* See comment on the above htab_clear_slot call.  */
-		    *hash_slot = file;
+		    *hash_slot = &hash_slot;
 		    htab_clear_slot (pfile->file_hash, hash_slot);
 		  }
 		return NULL;
diff --git a/libiberty/regex.c b/libiberty/regex.c
index 5531d877f0b..b6cb2320b56 100644
--- a/libiberty/regex.c
+++ b/libiberty/regex.c
@@ -30,6 +30,10 @@
   #pragma alloca
 #endif
 
+#if __GNUC__ >= 12
+#  pragma GCC diagnostic ignored "-Wuse-after-free"
+#endif
+
 #undef	_GNU_SOURCE
 #define _GNU_SOURCE
 

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

* [PATCH 2/2] add -Wdangling-pointer [PR #63272]
  2021-11-01 22:15 [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
@ 2021-11-01 22:18 ` Martin Sebor
  2021-11-02  7:40   ` Eric Gallager
  2021-11-30 22:55   ` [PATCH v2 " Martin Sebor
  2021-11-08 22:41 ` PING [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
  2 siblings, 2 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-01 22:18 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

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

Patch 2 in this series adds support for detecting the uses of
dangling pointers: those to auto objects that have gone out of
scope.  Like patch 1, to minimize false positives this detection
is very simplistic.  However, thanks to the more deterministic
nature of the problem (all local objects go out of scope) is able
to detect more instances of it.  The approach I used is to simply
search the IL for clobbers that dominate uses of pointers to
the clobbered objects.  If such a use is found that's not
followed by a clobber of the same object the warning triggers.
Similar to -Wuse-after-free, the new -Wdangling-pointer option
has multiple levels: level 1 to detect unconditional uses and
level 2 to flag conditional ones.  Unlike with -Wuse-after-free
there is no use case for testing dangling pointers for
equality, so there is no level 3.

Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
It found no problems outside of the GCC test suite.

As with the first patch in this series, the tests contain a number
of xfails due to known limitations marked with pr??????.  I'll
open bugs for them before committing the patch if I don't resolve
them first in a followup.

Martin

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

Add -Wdangling-pointer [PR63272].
Resolves:

PR c/63272 - GCC should warn when using pointer to dead scoped variable within the same function

gcc/c-family/ChangeLog:

	PR c/63272
	* c.opt:

gcc/ChangeLog:

	PR c/63272
	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
	-Wdangling-pointer.
	* doc/invoke.texi (-Wdangling-pointer): Document new option.
	* gimple-ssa-isolate-paths.c (diag_returned_locals): Suppress
	warning after issuing it.
	* gimple-ssa-warn-access.cc (pass_waccess::clone): Set new member.
	(pass_waccess::check_pointer_uses): New function.
	(pass_waccess::gimple_call_return_arg): New function.
	(pass_waccess::gimple_call_return_arg_ref): New function.
	(pass_waccess::check_call_dangling): New function.
	(pass_waccess::check_dangling_uses): New function overloads.
	(pass_waccess::check_dangling_stores): New function.
	(pass_waccess::check_dangling_stores): New function.
	(pass_waccess::m_clobbers): New data member.
	(pass_waccess::m_func): New data member.
	(pass_waccess::m_run_number): New data member.
	(pass_waccess::m_check_dangling_p): New data member.
	(pass_waccess::check_alloca): Check m_early_checks_p.
	(pass_waccess::check_alloc_size_call): Same.
	(pass_waccess::check_strcat): Same.
	(pass_waccess::check_strncat): Same.
	(pass_waccess::check_stxcpy): Same.
	(pass_waccess::check_stxncpy): Same.
	(pass_waccess::check_strncmp): Same.
	(pass_waccess::check_memop_access): Same.
	(pass_waccess::check_read_access): Same.
	(pass_waccess::check_builtin): Call check_pointer_uses.
	(pass_waccess::warn_invalid_pointer): Add arguments.
	(is_auto_decl): New function.
	(pass_waccess::check_stmt): New function.
	(pass_waccess::check_block): Call check_stmt.
	(pass_waccess::execute): Call check_dangling_uses,
	check_dangling_stores.  Empty m_clobbers.
	* passes.def (pass_warn_access): Invoke pass two more times.

gcc/testsuite/ChangeLog:

	PR c/63272
	* g++.dg/warn/Wfree-nonheap-object-6.C: Disable valid warnings.
	* gcc.dg/uninit-pr50476.c: Expect a new warning.
	* c-c++-common/Wdangling-pointer-2.c: New test.
	* c-c++-common/Wdangling-pointer-3.c: New test.
	* c-c++-common/Wdangling-pointer-4.c: New test.
	* c-c++-common/Wdangling-pointer-5.c: New test.
	* c-c++-common/Wdangling-pointer.c: New test.
	* gcc.dg/Wdangling-pointer-2.c: New test.
	* gcc.dg/Wdangling-pointer.c: New test.


diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a5fe00ed195..6aa04721075 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -524,6 +524,14 @@ Wdangling-else
 C ObjC C++ ObjC++ Var(warn_dangling_else) Warning LangEnabledBy(C ObjC C++ ObjC++,Wparentheses)
 Warn about dangling else.
 
+Wdangling-pointer
+C ObjC C++ LTO ObjC++ Alias(Wdangling-pointer=, 2, 0) Warning
+Warn for uses of pointers to auto variables whose lifetime has ended.
+
+Wdangling-pointer=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2)
+Warn for uses of pointers to auto variables whose lifetime has ended.
+
 Wdate-time
 C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning
 Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage.
diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c
index 0d68af4d91e..ac2ec0c13ce 100644
--- a/gcc/diagnostic-spec.c
+++ b/gcc/diagnostic-spec.c
@@ -99,6 +99,7 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt)
 	m_bits = NW_UNINIT;
       break;
 
+    case OPT_Wdangling_pointer_:
     case OPT_Wreturn_local_addr:
     case OPT_Wuse_after_free_:
       m_bits = NW_DANGLING;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index eb4ecb56dcc..cdbd9991da2 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -339,7 +339,8 @@ Objective-C and Objective-C++ Dialects}.
 -Wchar-subscripts @gol
 -Wclobbered  -Wcomment @gol
 -Wconversion  -Wno-coverage-mismatch  -Wno-cpp @gol
--Wdangling-else  -Wdate-time @gol
+-Wdangling-else  -Wdangling-pointer  -Wdangling-pointer=@var{n}  @gol
+-Wdate-time @gol
 -Wno-deprecated  -Wno-deprecated-declarations  -Wno-designated-init @gol
 -Wdisabled-optimization @gol
 -Wno-discarded-array-qualifiers  -Wno-discarded-qualifiers @gol
@@ -5667,6 +5668,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wcatch-value @r{(C++ and Objective-C++ only)}  @gol
 -Wchar-subscripts  @gol
 -Wcomment  @gol
+-Wdangling-pointer=2  @gol
 -Wduplicate-decl-specifier @r{(C and Objective-C only)} @gol
 -Wenum-compare @r{(in C/ObjC; this is on by default in C++)} @gol
 -Wformat   @gol
@@ -8503,6 +8505,50 @@ looks like this:
 
 This warning is enabled by @option{-Wparentheses}.
 
+@item -Wdangling-pointer
+@itemx -Wdangling-pointer=@var{n}
+@opindex Wdangling-pointer
+@opindex Wno-dangling-pointer
+Warn about uses of pointers to objects with automatic storage duration after
+their lifetime has ended.  This includes local variables declared in nested
+blocks and compound literals.
+
+@table @gcctabopt
+@item -Wdangling-pointer=1
+At level 1 the warning diagnoses only unconditional uses of dangling pointers.
+For example
+@smallexample
+int f (int c1, int c2, x)
+@{
+  char *p = strchr ((char[])@{ c1, c2 @}, c3);
+  return p ? *p : 'x';   // warning: dangling pointer to a compound literal
+@}
+@end smallexample
+
+@item -Wdangling-pointer=2
+At level 2, in addition to unconditional uses the warning also diagnoses
+conditional uses of dangling pointers.
+
+For example, because the array @var{a} in the following function is out of
+scope when the pointer @var{s} that was set to point is used, the warning
+triggers at this level.
+
+@smallexample
+void f (char *s)
+@{
+  if (!s)
+    @{
+      char a[12] = "tmpname";
+      s = a;
+    @}
+  strcat (s, ".tmp");   // warning: dangling pointer to a may be used
+  ...
+@}
+@end smallexample
+@end table
+
+@option{-Wdangling-pointer=2} is included in @option{-Wall}.
+
 @item -Wdate-time
 @opindex Wdate-time
 @opindex Wno-date-time
diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
index 2dafe849ad3..3a6e28b6926 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -415,6 +415,7 @@ diag_returned_locals (bool maybe, const locmap_t &locmap)
 	{
 	  for (unsigned i = 0; i != nargs; ++i)
 	    inform (argsloc.locvec[i], "declared here");
+	  suppress_warning (stmt, OPT_Wreturn_local_addr);
 	}
     }
 }
diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index 2065402a2b9..da88c2ad421 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -2066,10 +2066,12 @@ class pass_waccess : public gimple_opt_pass
 
   ~pass_waccess ();
 
-  opt_pass *clone () { return new pass_waccess (m_ctxt); }
+  opt_pass *clone ();
 
   virtual bool gate (function *);
 
+  void set_pass_param (unsigned, bool);
+
   virtual unsigned int execute (function *);
 
 private:
@@ -2086,6 +2088,9 @@ private:
   /* Check a call to an ordinary function for invalid accesses.  */
   bool check_call_access (gcall *);
 
+  /* Check a non-call statement.  */
+  void check_stmt (gimple *);
+
   /* Check statements in a basic block.  */
   void check_block (basic_block);
 
@@ -2107,17 +2112,37 @@ private:
   void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *);
 
   /* Check for uses of indeterminate pointers.  */
-  void check_pointer_uses (gimple *, tree);
+  void check_pointer_uses (gimple *, tree, tree = NULL_TREE, bool = false);
 
   /* Return the argument that a call returns.  */
   tree gimple_call_return_arg (gcall *);
+  tree gimple_call_return_arg_ref (gcall *);
+
+  /* Check a call for uses of a dangling pointer arguments.  */
+  void check_call_dangling (gcall *);
+
+  /* Check uses of a dangling pointer or those derived from it.  */
+  void check_dangling_uses (tree, tree, bool = false);
+  void check_dangling_uses ();
+  void check_dangling_stores ();
+  void check_dangling_stores (basic_block, hash_set<tree> &, auto_bitmap &);
 
-  void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
+  void warn_invalid_pointer (tree, gimple *, gimple *, tree, bool, bool = false);
 
-/* A pointer_query object and its cache to store information about
+  /* A pointer_query object and its cache to store information about
      pointers and their targets in.  */
   pointer_query m_ptr_qry;
   pointer_query::cache_type m_var_cache;
+  /* Mapping from DECLs and their clobber statements in the function.  */
+  hash_map<tree, gimple *> m_clobbers;
+  /* The current function.  */
+  function *m_func;
+  /* The 1-based invocation number of the pass.  */
+  unsigned m_run_number;
+  /* True to run checks for uses of dangling pointers.  */
+  bool m_check_dangling_p;
+  /* True to run checks early on in the optimization pipeline.  */
+  bool m_early_checks_p;
 };
 
 /* Construct the pass.  */
@@ -2125,10 +2150,25 @@ private:
 pass_waccess::pass_waccess (gcc::context *ctxt)
   : gimple_opt_pass (pass_data_waccess, ctxt),
     m_ptr_qry (NULL, &m_var_cache),
-    m_var_cache ()
+    m_var_cache (),
+    m_clobbers (),
+    m_func (),
+    m_run_number (1),
+    m_check_dangling_p (),
+    m_early_checks_p ()
 {
 }
 
+/* Return a copy of the pass with RUN_NUMBER one greater than THIS.  */
+
+opt_pass*
+pass_waccess::clone ()
+{
+  pass_waccess *p = new pass_waccess (m_ctxt);
+  p->m_run_number = m_run_number + 1;
+  return p;
+}
+
 /* Release pointer_query cache.  */
 
 pass_waccess::~pass_waccess ()
@@ -2136,6 +2176,14 @@ pass_waccess::~pass_waccess ()
   m_ptr_qry.flush_cache ();
 }
 
+void
+pass_waccess::set_pass_param (unsigned int n, bool early)
+{
+  gcc_assert (n == 0);
+
+  m_early_checks_p = early;
+}
+
 /* Return true when any checks performed by the pass are enabled.  */
 
 bool
@@ -2324,6 +2372,9 @@ maybe_warn_alloc_args_overflow (gimple *stmt, const tree args[2],
 void
 pass_waccess::check_alloca (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if ((warn_vla_limit >= HOST_WIDE_INT_MAX
        && warn_alloc_size_limit < warn_vla_limit)
       || (warn_alloca_limit >= HOST_WIDE_INT_MAX
@@ -2345,6 +2396,9 @@ pass_waccess::check_alloca (gcall *stmt)
 void
 pass_waccess::check_alloc_size_call (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if (gimple_call_num_args (stmt) < 1)
     /* Avoid invalid calls to functions without a prototype.  */
     return;
@@ -2395,6 +2449,9 @@ pass_waccess::check_alloc_size_call (gcall *stmt)
 void
 pass_waccess::check_strcat (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if (!warn_stringop_overflow && !warn_stringop_overread)
     return;
 
@@ -2420,6 +2477,9 @@ pass_waccess::check_strcat (gcall *stmt)
 void
 pass_waccess::check_strncat (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if (!warn_stringop_overflow && !warn_stringop_overread)
     return;
 
@@ -2489,6 +2549,9 @@ pass_waccess::check_strncat (gcall *stmt)
 void
 pass_waccess::check_stxcpy (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   tree dst = call_arg (stmt, 0);
   tree src = call_arg (stmt, 1);
 
@@ -2527,7 +2590,7 @@ pass_waccess::check_stxcpy (gcall *stmt)
 void
 pass_waccess::check_stxncpy (gcall *stmt)
 {
-  if (!warn_stringop_overflow)
+  if (m_early_checks_p || !warn_stringop_overflow)
     return;
 
   tree dst = call_arg (stmt, 0);
@@ -2551,7 +2614,7 @@ pass_waccess::check_stxncpy (gcall *stmt)
 void
 pass_waccess::check_strncmp (gcall *stmt)
 {
-  if (!warn_stringop_overread)
+  if (m_early_checks_p || !warn_stringop_overread)
     return;
 
   tree arg1 = call_arg (stmt, 0);
@@ -2656,6 +2719,9 @@ pass_waccess::check_strncmp (gcall *stmt)
 void
 pass_waccess::check_memop_access (gimple *stmt, tree dest, tree src, tree size)
 {
+  if (m_early_checks_p)
+    return;
+
   /* For functions like memset and memcpy that operate on raw memory
      try to determine the size of the largest source and destination
      object using type-0 Object Size regardless of the object size
@@ -2677,7 +2743,7 @@ pass_waccess::check_read_access (gimple *stmt, tree src,
 				 tree bound /* = NULL_TREE */,
 				 int ost /* = 1 */)
 {
-  if (!warn_stringop_overread)
+  if (m_early_checks_p || !warn_stringop_overread)
     return;
 
   if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound)))
@@ -2805,11 +2871,12 @@ pass_waccess::check_builtin (gcall *stmt)
 
     case BUILT_IN_FREE:
     case BUILT_IN_REALLOC:
-      {
-	tree arg = call_arg (stmt, 0);
-	if (TREE_CODE (arg) == SSA_NAME)
-	  check_pointer_uses (stmt, arg);
-      }
+      if (!m_early_checks_p)
+	{
+	  tree arg = call_arg (stmt, 0);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    check_pointer_uses (stmt, arg);
+	}
       return true;
 
     case BUILT_IN_GETTEXT:
@@ -3487,12 +3554,15 @@ gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
 
 /* Issue a warning for the USE_STMT of pointer PTR rendered invalid
    by INVAL_STMT.  PTR may be null when it's been optimized away.
-   MAYBE is true to issue the "maybe" kind of warning.  EQUALITY is
-   true when the pointer is used in an equality expression.  */
+   When nonnull, CALLEE is the deallocation function that rendered
+   the pointer dangling.  Otherwise, VAR is the auto variable or
+   compound literal whose lifetime's rended it dangling.  MAYBE is
+   true to issue the "maybe" kind of warning.  EQUALITY is true when
+   the pointer is used in an equality expression.  */
 
 void
 pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
-				    gimple *inval_stmt,
+				    gimple *inval_stmt, tree var,
 				    bool maybe,
 				    bool equality /* = false */)
 {
@@ -3504,7 +3574,7 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
   location_t use_loc = gimple_location (use_stmt);
   if (use_loc == UNKNOWN_LOCATION)
     {
-      use_loc = cfun->function_end_locus;
+      use_loc = m_func->function_end_locus;
       if (!ptr)
 	/* Avoid issuing a warning with no context other than
 	   the function.  That would make it difficult to debug
@@ -3538,6 +3608,52 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
 	}
       return;
     }
+
+  if ((maybe && warn_dangling_pointer < 2)
+      || warning_suppressed_p (use_stmt, OPT_Wdangling_pointer_))
+    return;
+
+  if (DECL_NAME (var))
+    {
+      if ((ptr
+	   && warning_at (use_loc, OPT_Wdangling_pointer_,
+			  (maybe
+			   ? G_("dangling pointer %qE to %qD may be used")
+			   : G_("using dangling pointer %qE to %qD")),
+			  ptr, var))
+	  || (!ptr
+	      && warning_at (use_loc, OPT_Wdangling_pointer_,
+			     (maybe
+			      ? G_("dangling pointer to %qD may be used")
+			      : G_("using a dangling pointer to %qD")),
+			     var)))
+	inform (DECL_SOURCE_LOCATION (var),
+		"%qD declared here", var);
+      suppress_warning (use_stmt, OPT_Wdangling_pointer_);
+      return;
+    }
+
+  if ((ptr
+       && warning_at (use_loc, OPT_Wdangling_pointer_,
+		      (maybe
+		       ? G_("dangling pointer %qE to a compound literal "
+			    "may be used")
+		       : G_("using dangling pointer %qE to a compound "
+			    "literal")),
+		      ptr, var))
+      || (!ptr
+	  && warning_at (use_loc, OPT_Wdangling_pointer_,
+			 (maybe
+			  ? G_("dangling pointer to a compound literal "
+			       "may be used")
+			  : G_("using a dangling pointer to a compound "
+			       "literal")),
+			 var)))
+    {
+      inform (DECL_SOURCE_LOCATION (var),
+	      "compound literal defined here");
+      suppress_warning (use_stmt, OPT_Wdangling_pointer_);
+    }
 }
 
 /* If STMT is a call to either the standard realloc or to a user-defined
@@ -3660,10 +3776,14 @@ pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
 
 /* For a STMT either a call to a deallocation function or a clobber, warn
    for uses of the pointer PTR it was called with (including its copies
-   or others derived from it by pointer arithmetic).  */
+   or others derived from it by pointer arithmetic).  If STMT is a clobber,
+   VAR is the decl of the clobbered variable.  When MAYBE is true use
+   a "maybe" form of diagnostic.  */
 
 void
-pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
+pass_waccess::check_pointer_uses (gimple *stmt, tree ptr,
+				  tree var /* = NULL_TREE */,
+				  bool maybe /* = false */)
 {
   gcc_assert (TREE_CODE (ptr) == SSA_NAME);
 
@@ -3672,7 +3792,7 @@ pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
 
   /* If the deallocation (or clobber) statement dominates more than
      a single basic block issue a "maybe" kind of warning.  */
-  bool maybe = !single_succ_p (stmt_bb);
+  maybe |= !single_succ_p (stmt_bb);
 
   /* If STMT is a reallocation function set to the reallocated pointer
      and the LHS of the call, respectively.  */
@@ -3752,13 +3872,19 @@ pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
 	     of any other pointers derived from it can be checked.  */
 	  if (gimple_use_after_inval_p (stmt, use_stmt, check_dangling))
 	    {
-	      /* TODO: Handle PHIs but careful of false positives.  */
-	      if (gimple_code (use_stmt) != GIMPLE_PHI)
+	      if (gimple_code (use_stmt) == GIMPLE_PHI)
 		{
-		  warn_invalid_pointer (*use_p->use, use_stmt, stmt,
-					maybe, equality);
-		  continue;
+		  tree lhs = gimple_phi_result (use_stmt);
+		  if (TREE_CODE (lhs) == SSA_NAME)
+		    {
+		      pointers.safe_push (lhs);
+		      continue;
+		    }
 		}
+		
+	      warn_invalid_pointer (*use_p->use, use_stmt, stmt, var,
+				    maybe, equality);
+	      continue;
 	    }
 
 	  if (is_gimple_assign (use_stmt))
@@ -3793,26 +3919,99 @@ pass_waccess::check_call (gcall *stmt)
   if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
     check_builtin (stmt);
 
-  if (tree callee = gimple_call_fndecl (stmt))
-    {
-      /* Check for uses of the pointer passed to either a standard
-	 or a user-defined deallocation function.  */
-      unsigned argno = fndecl_dealloc_argno (callee);
-      if (argno < (unsigned) call_nargs (stmt))
-	{
-	  tree arg = call_arg (stmt, argno);
-	  if (TREE_CODE (arg) == SSA_NAME)
-	    check_pointer_uses (stmt, arg);
-	}
-    }
+  if (!m_early_checks_p)
+    if (tree callee = gimple_call_fndecl (stmt))
+      {
+	/* Check for uses of the pointer passed to either a standard
+	   or a user-defined deallocation function.  */
+	unsigned argno = fndecl_dealloc_argno (callee);
+	if (argno < (unsigned) call_nargs (stmt))
+	  {
+	    tree arg = call_arg (stmt, argno);
+	    if (TREE_CODE (arg) == SSA_NAME)
+	      check_pointer_uses (stmt, arg);
+	  }
+      }
 
   check_call_access (stmt);
+  check_call_dangling (stmt);
+
+  if (m_early_checks_p)
+    return;
 
   maybe_check_dealloc_call (stmt);
   check_nonstring_args (stmt);
 }
 
 
+/* Return true of X is a DECL with automatic storage duration.  */
+
+static inline bool
+is_auto_decl (tree x)
+{
+  return DECL_P (x) && !DECL_EXTERNAL (x) && !TREE_STATIC (x);
+}
+
+/* Check non-call STMT for invalid accesses.  */
+
+void
+pass_waccess::check_stmt (gimple *stmt)
+{
+  if (m_check_dangling_p && gimple_clobber_p (stmt))
+    {
+      /* Ignore clobber statemts in blocks with exceptional edges.  */
+      basic_block bb = gimple_bb (stmt);
+      edge e = EDGE_PRED (bb, 0);
+      if (e->flags & EDGE_EH)
+	return;
+
+      tree var = gimple_assign_lhs (stmt);
+      m_clobbers.put (var, stmt);
+      return;
+    }
+
+  if (is_gimple_assign (stmt))
+    {
+      /* Clobbered compound literals can be revived.  Check for
+	 an assignment to one and remove it from M_CLOBBERS.  */
+      tree lhs = gimple_assign_lhs (stmt);
+      while (handled_component_p (lhs))
+	lhs = TREE_OPERAND (lhs, 0);
+
+      if (is_auto_decl (lhs))
+	m_clobbers.remove (lhs);
+      return;
+    }
+
+  if (greturn *ret = dyn_cast <greturn *> (stmt))
+    {
+      if (optimize && flag_isolate_erroneous_paths_dereference)
+	/* Avoid interfering with -Wreturn-local-addr (which runs only
+	   with optimization enabled).  */
+	return;
+
+      tree arg = gimple_return_retval (ret);
+      if (!arg || TREE_CODE (arg) != ADDR_EXPR)
+	return;
+
+      arg = TREE_OPERAND (arg, 0);
+      while (handled_component_p (arg))
+	arg = TREE_OPERAND (arg, 0);
+
+      if (!is_auto_decl (arg))
+	return;
+
+      gimple **pclobber = m_clobbers.get (arg);
+      if (!pclobber)
+	return;
+
+      if (!gimple_use_after_inval_p (*pclobber, stmt))
+	return;
+
+      warn_invalid_pointer (NULL_TREE, stmt, *pclobber, arg, false);
+    }
+}
+
 /* Check basic block BB for invalid accesses.  */
 
 void
@@ -3825,6 +4024,8 @@ pass_waccess::check_block (basic_block bb)
       gimple *stmt = gsi_stmt (si);
       if (gcall *call = dyn_cast <gcall *> (stmt))
 	check_call (call);
+      else
+	check_stmt (stmt);
     }
 }
 
@@ -3873,6 +4074,232 @@ pass_waccess::gimple_call_return_arg (gcall *call)
   return gimple_call_arg (call, argno);
 }
 
+/* Return the decl referenced by the argument that the call STMT to
+   a built-in function returns (including with an offset) or null if
+   it doesn't.  */
+
+tree
+pass_waccess::gimple_call_return_arg_ref (gcall *call)
+{
+  if (tree arg = gimple_call_return_arg (call))
+    {
+      access_ref aref;
+      if (m_ptr_qry.get_ref (arg, call, &aref, 0)
+	  && DECL_P (aref.ref))
+	return aref.ref;
+    }
+
+  return NULL_TREE;
+}
+
+/* Check for and diagnose all uses of the dangling pointer VAR to
+   the auto object DECL whose lifetime has ended.  */
+
+void
+pass_waccess::check_dangling_uses (tree var, tree decl, bool maybe /* = false */)
+{
+  if (!decl || !is_auto_decl (decl))
+    return;
+
+  gimple **pclob = m_clobbers.get (decl);
+  if (!pclob)
+    return;
+
+  check_pointer_uses (*pclob, var, decl, maybe);
+}
+
+/* Diagnose stores in BB and (recursively) its predecessors of the addresses
+   of local variables into nonlocal pointers that are left dangling after
+   the function returns.  BBS is a bitmap of basic blocks visited.  */
+
+void
+pass_waccess::check_dangling_stores (basic_block bb,
+				     hash_set<tree> &stores,
+				     auto_bitmap &bbs)
+{
+  if (!bitmap_set_bit (bbs, bb->index))
+    /* Avoid cycles. */
+    return;
+
+  /* Iterate backwards over the statements looking for a store of
+     the address of a local variable into a nonlocal pointer.  */
+  for (auto gsi = gsi_last_nondebug_bb (bb); ; gsi_prev_nondebug (&gsi))
+    {
+      gimple *stmt = gsi_stmt (gsi);
+      if (!stmt)
+	break;
+
+      if (is_gimple_call (stmt)
+	  && !(gimple_call_flags (stmt) & (ECF_CONST | ECF_PURE)))
+	/* Avoid looking before nonconst, nonpure calls since those might
+	   use the escaped locals.  */
+	return;
+
+      if (!is_gimple_assign (stmt) || gimple_clobber_p (stmt))
+	continue;
+
+      access_ref lhs_ref;
+      tree lhs = gimple_assign_lhs (stmt);
+      if (!m_ptr_qry.get_ref (lhs, stmt, &lhs_ref, 0))
+	continue;
+
+      if (is_auto_decl (lhs_ref.ref))
+	continue;
+
+      if (DECL_P (lhs_ref.ref))
+	{
+	  if (!POINTER_TYPE_P (TREE_TYPE (lhs_ref.ref))
+	      || lhs_ref.deref > 0)
+	    continue;
+	}
+      else if (TREE_CODE (lhs_ref.ref) == SSA_NAME)
+	{
+	  /* Avoid looking at or before stores into unknown objects.  */
+	  gimple *def_stmt = SSA_NAME_DEF_STMT (lhs_ref.ref);
+	  if (!gimple_nop_p (def_stmt))
+	    return;
+	}
+      else if (TREE_CODE (lhs_ref.ref) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (lhs_ref.ref, 0);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    {
+	      gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
+	      if (!gimple_nop_p (def_stmt))
+		return;
+	    }
+	}
+      else
+	continue;
+
+      if (stores.add (lhs_ref.ref))
+	continue;
+
+      access_ref rhs_ref;
+      tree rhs = gimple_assign_rhs1 (stmt);
+      if (!m_ptr_qry.get_ref (rhs, stmt, &rhs_ref, 0)
+	  || rhs_ref.deref != -1)
+	continue;
+
+      if (!is_auto_decl (rhs_ref.ref))
+	continue;
+
+      location_t loc = gimple_location (stmt);
+      if (warning_at (loc, OPT_Wdangling_pointer_,
+		      "storing the address of local variable %qD in %qE",
+		      rhs_ref.ref, lhs))
+	{
+	  location_t loc = DECL_SOURCE_LOCATION (rhs_ref.ref);
+	  inform (loc, "%qD declared here", rhs_ref.ref);
+
+	  if (DECL_P (lhs_ref.ref))
+	    loc = DECL_SOURCE_LOCATION (lhs_ref.ref);
+	  else if (EXPR_HAS_LOCATION (lhs_ref.ref))
+	    loc = EXPR_LOCATION (lhs_ref.ref);
+
+	  if (loc != UNKNOWN_LOCATION)
+	    inform (loc, "%qE declared here", lhs_ref.ref);
+	}
+    }
+
+  edge e;
+  edge_iterator ei;
+  FOR_EACH_EDGE (e, ei, bb->preds)
+    {
+      basic_block pred = e->src;
+      check_dangling_stores (pred, stores, bbs);
+    }
+}
+
+/* Diagnose stores of the addresses of local variables into nonlocal
+   pointers that are left dangling after the function returns.  */
+
+void
+pass_waccess::check_dangling_stores ()
+{
+  auto_bitmap bbs;
+  hash_set<tree> stores;
+  check_dangling_stores (EXIT_BLOCK_PTR_FOR_FN (m_func), stores, bbs);
+}
+
+/* Check for and diagnose uses of dangling pointers to auto objects
+   whose lifetime has ended.  */
+
+void
+pass_waccess::check_dangling_uses ()
+{
+  tree var;
+  unsigned i;
+  FOR_EACH_SSA_NAME (i, var, m_func)
+    {
+      if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+	continue;
+
+      /* For each SSA_NAME pointer VAR find the DECL it points to.
+	 If the DECL is a clobbered local variable, check to see
+	 if any of VAR's uses (or those of other pointers derived
+	 from VAR) happens after the clobber.  If so, warn.  */
+      tree decl = NULL_TREE;
+
+      gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+      if (is_gimple_assign (def_stmt))
+	{
+	  tree rhs = gimple_assign_rhs1 (def_stmt);
+	  if (TREE_CODE (rhs) == ADDR_EXPR)
+	    decl = TREE_OPERAND (rhs, 0);
+	}
+      else if (gcall *call = dyn_cast<gcall *>(def_stmt))
+	decl = gimple_call_return_arg_ref (call);
+      else if (gphi *phi = dyn_cast <gphi *>(def_stmt))
+	{
+	  unsigned nargs = gimple_phi_num_args (phi);
+	  for (unsigned i = 0; i != nargs; ++i)
+	    {
+	      access_ref aref;
+	      tree arg = gimple_phi_arg_def (phi, i);
+	      if (!m_ptr_qry.get_ref (arg, phi, &aref, 0)
+		  || (aref.deref == 0
+		      && POINTER_TYPE_P (TREE_TYPE (aref.ref))))
+		continue;
+	      check_dangling_uses (var, aref.ref, true);
+	    }
+	  continue;
+	}
+      else
+	continue;
+
+      check_dangling_uses (var, decl);
+    }
+}
+
+/* Check CALL arguments for dangling pointers (those that have been
+   clobbered) and warn if found.  */
+
+void
+pass_waccess::check_call_dangling (gcall *call)
+{
+  unsigned nargs = gimple_call_num_args (call);
+  for (unsigned i = 0; i != nargs; ++i)
+    {
+      tree arg = gimple_call_arg (call, i);
+      if (TREE_CODE (arg) != ADDR_EXPR)
+	continue;
+
+      arg = TREE_OPERAND (arg, 0);
+      if (!DECL_P (arg))
+	continue;
+
+      gimple **pclobber = m_clobbers.get (arg);
+      if (!pclobber)
+	continue;
+
+      if (!gimple_use_after_inval_p (*pclobber, call))
+	continue;
+
+      warn_invalid_pointer (NULL_TREE, call, *pclobber, arg, false);
+    }
+}
+
 /* Check function FUN for invalid accesses.  */
 
 unsigned
@@ -3882,11 +4309,23 @@ pass_waccess::execute (function *fun)
 
   /* Create a new ranger instance and associate it with FUN.  */
   m_ptr_qry.rvals = enable_ranger (fun);
+  m_func = fun;
+
+  /* Check for dangling pointers in the earliest run of the pass, either
+     the first run when optimization is disabled, or in the second run
+     when  it's enabled.  */
+  m_check_dangling_p = m_early_checks_p && (m_run_number == 1 + !!optimize);
 
   basic_block bb;
   FOR_EACH_BB_FN (bb, fun)
     check_block (bb);
 
+  if (m_check_dangling_p)
+    {
+      check_dangling_uses ();
+      check_dangling_stores ();
+    }
+
   if (dump_file)
     m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
 
@@ -3897,6 +4336,8 @@ pass_waccess::execute (function *fun)
   disable_ranger (fun);
   m_ptr_qry.rvals = NULL;
 
+  m_clobbers.empty ();
+
   free_dominance_info (CDI_DOMINATORS);
   return 0;
 }
diff --git a/gcc/passes.def b/gcc/passes.def
index 0f541454e7f..1374302cab9 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -63,6 +63,8 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_ubsan);
       NEXT_PASS (pass_nothrow);
       NEXT_PASS (pass_rebuild_cgraph_edges);
+      /* This instance runs only when optimization is disabled.  */
+      NEXT_PASS (pass_warn_access, /*early=*/true);
   POP_INSERT_PASSES ()
 
   NEXT_PASS (pass_local_optimization_passes);
@@ -200,6 +202,9 @@ along with GCC; see the file COPYING3.  If not see
 	 form if possible.  */
       NEXT_PASS (pass_object_sizes);
       NEXT_PASS (pass_post_ipa_warn);
+      /* This early instance runs only with optimization enabled.
+      	 Must run before loop unrolling.  */
+      NEXT_PASS (pass_warn_access, /*early=*/true);
       NEXT_PASS (pass_complete_unrolli);
       NEXT_PASS (pass_backprop);
       NEXT_PASS (pass_phiprop);
@@ -421,7 +426,7 @@ along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_harden_compares);
   NEXT_PASS (pass_cleanup_cfg_post_optimizing);
   NEXT_PASS (pass_warn_function_noreturn);
-  NEXT_PASS (pass_warn_access);
+  NEXT_PASS (pass_warn_access, /*early=*/false);
 
   NEXT_PASS (pass_expand);
 
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
new file mode 100644
index 00000000000..206aab6aa91
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
@@ -0,0 +1,352 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise basic cases of -Wdangling-pointer with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-uninitialized -fno-isolate-erroneous-paths-dereference -ftrack-macro-expansion=0" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+#define NOIPA __attribute__ ((noipa))
+
+EXTERN_C void* alloca (size_t);
+EXTERN_C void* malloc (size_t);
+EXTERN_C void* memchr (const void*, int, size_t);
+
+int sink (const void*, ...);
+#define sink(...) sink (0, __VA_ARGS__)
+
+
+NOIPA void nowarn_addr (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };
+    p = a;
+  }
+
+  // This is suspect but not a clear error.
+  sink (&p);
+}
+
+
+NOIPA char* nowarn_ptr (void)
+{
+  char *p;
+  sink (&p);
+  return p;
+}
+
+
+NOIPA char* nowarn_cond_ptr (void)
+{
+  // Distilled from a false positive in Glibc dlerror.c.
+  char *q;
+  if (sink (&q))
+    return q;
+
+  return 0;
+}
+
+
+NOIPA void nowarn_loop_ptr (int n, int *p)
+{
+  // Distilled from a false positive in Glibc td_thr_get_info.c.
+  for (int i = 0; i != 2; ++i)
+    {
+      int x;
+      sink (&x);
+      *p++ = x;
+    }
+
+  /* With the loop unrolled, Q is clobbered just before the call to
+     sink(), making it indistinguishable from passing it a pointer
+     to an out-of-scope variable.  Verify that the warning doesn't
+     suffer from false positives due to this.
+     int * q;
+     int * q.1_17;
+     int * q.1_26;
+
+     <bb 2>:
+     f (&q);
+     q.1_17 = q;
+     *p_5(D) = q.1_17;
+     q ={v} {CLOBBER};
+     f (&q);
+     q.1_26 = q;
+     MEM[(void * *)p_5(D) + 8B] = q.1_26;
+     q ={v} {CLOBBER};
+     return;
+  */
+}
+
+
+NOIPA void nowarn_intptr_t (void)
+{
+  intptr_t ip;
+  {
+    int a[] = { 1, 2, 3 };
+    ip = (intptr_t)a;
+  }
+
+  // Using an intptr_t is not diagnosed.
+  sink (0, ip);
+}
+
+
+NOIPA void nowarn_string_literal (void)
+{
+  const char *s;
+  {
+    s = "123";
+  }
+
+  sink (s);
+}
+
+
+NOIPA void nowarn_extern_array (int x)
+{
+  {
+    /* This is a silly sanity check.  */
+    extern int eia[];
+    int *p;
+    {
+      p = eia;
+    }
+    sink (p);
+  }
+}
+
+
+NOIPA void nowarn_static_array (int x)
+{
+  {
+    const char *s;
+    {
+      static const char sca[] = "123";
+      s = sca;
+    }
+
+    sink (s);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = sia;
+    }
+
+    sink (p);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = (const int*)memchr (sia, x, sizeof sia);
+    }
+
+    sink (p);
+  }
+}
+
+
+NOIPA void nowarn_alloca (unsigned n)
+{
+  {
+    char *p;
+    {
+      p = (char*)alloca (n);
+    }
+    sink (p);
+  }
+  {
+    int *p;
+    {
+      p = (int*)alloca (n * sizeof *p);
+      sink (p);
+    }
+    sink (p);
+  }
+  {
+    long *p;
+    {
+      p = (long*)alloca (n * sizeof *p);
+      sink (p);
+      p = p + 1;
+    }
+    sink (p);
+  }
+}
+
+
+#pragma GCC diagnostic push
+/* Verify that -Wdangling-pointer works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wdangling-pointer"
+
+NOIPA void nowarn_scalar_call_ignored (void *vp)
+{
+  int *p;
+  {
+    int i;
+    p = &i;
+  }
+  sink (p);
+}
+
+#pragma GCC diagnostic pop
+
+NOIPA void warn_scalar_call (void)
+{
+  int *p;
+  {
+    int i;                    // { dg-message "'i' declared" "note" }
+    p = &i;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'i'" "array" }
+}
+
+
+NOIPA void warn_array_call (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" }
+}
+
+
+NOIPA void* warn_array_return (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+
+  return p;                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" }
+}
+
+
+NOIPA void warn_pr63272_c1 (int i)
+{
+  int *p = 0;
+
+  if (i)
+    {
+      int k = i;              // { dg-message "'k' declared" "pr63272" { xfail *-*-* } }
+      p = &k;
+    }
+
+  sink (p ? *p : 0);          // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'k'" "pr63272" { xfail *-*-* } }
+}
+
+
+NOIPA void warn_pr63272_c4 (void)
+{
+  int *p = 0;
+
+  {
+    int b;                    // { dg-message "'b' declared" "note" }
+    p = &b;
+  }
+
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'b'" "scalar" }
+}
+
+
+void warn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+}
+
+
+void warn_cond_else (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int *a = (int*)malloc (n);
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 2, 3 };
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
+
+
+void warn_cond_if_else (int i)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 3, 4 };      // { dg-message "'b' declared" "pr??????" { xfail *-*-* } }
+     sink (b);
+     p = b;
+   }
+
+  /* With a PHI with more than invalid argument, only one use is diagnosed
+     because after the first diagnostic the code suppresses subsequent
+     ones for the same use.  This needs to be fixed.  */
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+                              // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 }
+}
+
+
+void nowarn_gcc_i386 (int i)
+{
+  // Regression test reduced from gcc's i386.c.
+  char a[32], *p;
+
+  if (i != 1)
+    p = a;
+  else
+    p = 0;
+
+  if (i == 2)
+    sink (p);
+  else
+    {
+      if (p)
+	{
+	  sink (p);
+	  return;
+	}
+      sink (p);
+    }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
new file mode 100644
index 00000000000..d2f8f432eba
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
@@ -0,0 +1,64 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise conditional uses dangling pointers with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-maybe-uninitialized" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* memcpy (void*, const void*, size_t);
+
+void sink (const void*, ...);
+
+char* nowarn_conditional (char *s)
+{
+  // Reduced from Glibc's tmpnam.c.
+  extern char a[5];
+  char b[5];
+  char *p = s ? s : b;
+
+  sink (p);
+
+  if (s == 0)
+    return a;
+
+  return s;
+}
+
+
+char* nowarn_conditional_memcpy (char *s)
+{
+  // Reduced from Glibc's tmpnam.c.
+  extern char a[5];
+  char b[5];
+  char *p = s ? s : b;
+
+  sink (p);
+
+  if (s == 0)
+    return (char*)memcpy (a, p, 5);
+
+  return s;
+}
+
+
+int warn_conditional_block (int i)
+{
+  int *p;
+  if (i)
+  {
+    int a[] = { 1, 2, 3 };
+    p = &a[i];
+  }
+  else
+    p = &i;
+
+  return *p;        // { dg-warning "dangling pointer \('p' \)to 'a' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
new file mode 100644
index 00000000000..e57e66f8336
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
@@ -0,0 +1,73 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise -Wdangling-pointer for VLAs.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */
+
+void sink (void*, ...);
+
+void nowarn_vla (int n)
+{
+  {
+    int vla1[n];
+    int *p1 = vla1;
+    sink (p1);
+
+    {
+      int vla2[n];
+      int *p2 = vla2;
+      sink (p1, p2);
+
+      {
+	int vla3[n];
+	int *p3 = vla3;
+	sink (p1, p2, p3);
+      }
+      sink (p1, p2);
+    }
+    sink (p1);
+  }
+}
+
+void warn_one_vla (int n)
+{
+  int *p;
+  {
+    int vla[n];               // { dg-message "'vla' declared" "pr??????" { xfail *-*-* } }
+    p = vla;
+  }
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla'" "vla" { xfail *-*-* } }
+}
+
+
+void warn_two_vlas_same_block (int n)
+{
+  int *p, *q;
+  {
+    int vla1[n];              // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } }
+    int vla2[n];              // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } }
+    p = vla1;
+    q = vla2;
+  }
+
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } }
+  sink (q);                   // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } }
+}
+
+
+void warn_two_vlas_in_series (int n)
+{
+  int *p;
+  {
+    int vla1[n];              // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } }
+    p = vla1;
+  }
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } }
+
+  int *q;
+  {
+    int vla2[n];              // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } }
+    q = vla2;
+  }
+  sink (q);                   // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
new file mode 100644
index 00000000000..23d37960fad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
@@ -0,0 +1,79 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise -Wdangling-pointer for VLAs.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */
+
+void* sink (void*, ...);
+
+extern void *evp;
+
+void nowarn_store_extern_call (void)
+{
+  int x;
+  evp = &x;
+  sink (0);
+}
+
+void nowarn_store_extern_ovrwrite (void)
+{
+  int x;
+  evp = &x;
+  evp = 0;
+}
+
+void nowarn_store_extern_store (void)
+{
+  int x;
+  void **p = (void**)sink (&evp);
+  evp = &x;
+  *p = 0;
+}
+
+
+void warn_store_extern (void)
+{
+  extern void *evp1;  // { dg-message "'evp1' declared here" }
+  int x;              // { dg-message "'x' declared here" }
+  evp1 = &x;          // { dg-warning "storing the address of local variable 'x' in 'evp1'" }
+}
+
+
+void nowarn_store_arg_call (void **vpp)
+{
+  int x;
+  *vpp = &x;
+  sink (0);
+}
+
+void nowarn_store_arg_ovrwrite (void **vpp)
+{
+  int x;
+  *vpp = &x;
+  *vpp = 0;
+}
+
+void nowarn_store_arg_store (void **vpp)
+{
+  int x;
+  void **p = (void**)sink (0);
+  *vpp = &x;
+  *p = 0;
+}
+
+void* nowarn_store_arg_store_arg (void **vpp1, void **vpp2)
+{
+  int x;
+  void **p = (void**)sink (0);
+  *vpp1 = &x;         // warn here?
+  *vpp2 = 0;          // might overwrite *vpp1
+  return p;
+}
+
+void warn_store_arg (void **vpp)
+{
+  int x;              // { dg-message "'x' declared here" }
+  *vpp = &x;          // { dg-warning "storing the address of local variable 'x' in '\\*vpp'" }
+}
+
+
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer.c b/gcc/testsuite/c-c++-common/Wdangling-pointer.c
new file mode 100644
index 00000000000..bf087df9571
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer.c
@@ -0,0 +1,348 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise basic cases of -Wdangling-pointer without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wno-uninitialized -ftrack-macro-expansion=0" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* alloca (size_t);
+EXTERN_C void* malloc (size_t);
+EXTERN_C void* memchr (const void*, int, size_t);
+
+int sink (const void*, ...);
+#define sink(...) sink (0, __VA_ARGS__)
+
+
+void nowarn_addr (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };
+    p = a;
+  }
+
+  // This is suspect but not a clear error.
+  sink (&p);
+}
+
+
+char* nowarn_ptr (void)
+{
+  char *p;
+  sink (&p);
+  return p;
+}
+
+
+char* nowarn_cond_ptr (void)
+{
+  // Distilled from a false positive in Glibc dlerror.c.
+  char *q;
+  if (sink (&q))
+    return q;
+
+  return 0;
+}
+
+
+void nowarn_loop_ptr (int n, int *p)
+{
+  // Distilled from a false positive in Glibc td_thr_get_info.c.
+  for (int i = 0; i != 2; ++i)
+    {
+      int x;
+      sink (&x);
+      *p++ = x;
+    }
+}
+
+
+void nowarn_intptr_t (void)
+{
+  intptr_t ip;
+  {
+    int a[] = { 1, 2, 3 };
+    ip = (intptr_t)a;
+  }
+
+  // Using an intptr_t is not diagnosed.
+  sink (0, ip);
+}
+
+
+void nowarn_string_literal (void)
+{
+  const char *s;
+  {
+    s = "123";
+  }
+
+  sink (s);
+}
+
+
+void nowarn_extern_array (int x)
+{
+  {
+    /* This is a silly sanity check.  */
+    extern int eia[];
+    int *p;
+    {
+      p = eia;
+    }
+    sink (p);
+  }
+}
+
+
+void nowarn_static_array (int x)
+{
+  {
+    const char *s;
+    {
+      static const char sca[] = "123";
+      s = sca;
+    }
+
+    sink (s);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = sia;
+    }
+
+    sink (p);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = (const int*)memchr (sia, x, sizeof sia);
+    }
+
+    sink (p);
+  }
+}
+
+
+void nowarn_alloca (unsigned n)
+{
+  {
+    char *p;
+    {
+      p = (char*)alloca (n);
+    }
+    sink (p);
+  }
+  {
+    int *p;
+    {
+      p = (int*)alloca (n * sizeof *p);
+      sink (p);
+    }
+    sink (p);
+  }
+  {
+    long *p;
+    {
+      p = (long*)alloca (n * sizeof *p);
+      sink (p);
+      p = p + 1;
+    }
+    sink (p);
+  }
+}
+
+
+#pragma GCC diagnostic push
+/* Verify that -Wdangling-pointer works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wdangling-pointer"
+
+void nowarn_scalar_call_ignored (void *vp)
+{
+  int *p;
+  {
+    int i;
+    p = &i;
+  }
+  sink (p);
+}
+
+#pragma GCC diagnostic pop
+
+
+void warn_scalar_call (void)
+{
+  int *p;
+  {
+    int i;                    // { dg-message "'i' declared" "note" }
+    p = &i;
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'i'" "array" }
+}
+
+
+void warn_array_call (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" "array" }
+}
+
+
+void* warn_array_return (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  return p;                   // { dg-warning "using dangling pointer 'p' to 'a'" "array" }
+}
+
+
+void warn_pr63272_c1 (int i)
+{
+  int *p = 0;
+
+  if (i)
+    {
+      int k = i;              // { dg-message "'k' declared" "note" }
+      p = &k;
+    }
+
+  sink (p ? *p : 0);          // { dg-warning "dangling pointer 'p' to 'k' may be used" }
+}
+
+
+void warn_pr63272_c4 (void)
+{
+  int *p = 0;
+
+  {
+    int b;                    // { dg-message "'b' declared" "note" }
+    p = &b;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'b'" "scalar" }
+}
+
+void nowarn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };
+      p = a;
+      sink (p);
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     p = b;
+     sink (p);
+   }
+
+  p = 0;
+}
+
+
+void warn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+}
+
+
+void warn_cond_else (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int *a = (int*)malloc (n);
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 2, 3 };
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
+
+
+void warn_cond_if_else (int i)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 3, 4 };      // { dg-message "'b' declared" "note" { xfail *-*-* } }
+     sink (b);
+     p = b;
+   }
+
+  /* With a PHI with more than invalid argument, only one use is diagnosed
+     because after the first diagnostic the code suppresses subsequent
+     ones for the same use.  This needs to be fixed.  */
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+                              // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 }
+}
+
+
+void nowarn_gcc_i386 (int i)
+{
+  // Regression test reduced from gcc's i386.c.
+  char a[32], *p;
+
+  if (i != 1)
+    p = a;
+  else
+    p = 0;
+
+  if (i == 2)
+    sink (p);
+  else
+    {
+      if (p)
+	{
+	  sink (p);
+	  return;
+	}
+      sink (p);
+    }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
index 83b6ff9157c..91a87786ae0 100644
--- a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O0 -Wall" } */
+   { dg-options "-O0 -Wall -Wno-dangling-pointer -Wno-return-local-address" } */
 
 #if __cplusplus < 201103L
 # define noexcept throw ()
@@ -18,6 +18,8 @@ extern void *p;
 void nowarn_placement_new ()
 {
   char a[sizeof (A)];
+  /* The store to the global p might trigger -Wdangling pointer or
+     -Wreturn-local-address (if/when it runs without optimization).  */
   p = new (a) A ();           // { dg-bogus "-Wfree-nonheap-object" }
 }
 
diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
new file mode 100644
index 00000000000..b5882fef69d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
@@ -0,0 +1,82 @@
+/* Exercise conditional C-only uses of dangling pointers with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memchr (const void*, int, size_t);
+extern char* strchr (const char*, int);
+
+void sink (void*, ...);
+
+
+void nowarn_compound_literal (int i, int j)
+{
+  {
+    int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 };
+    sink (p);
+  }
+  {
+    int a[] = { 1, 2, 3 };
+    int *q = i ? (int[]){ 4, 5, 6 } : a;
+    int *p = &q[1];
+    sink (p);
+  }
+  {
+    int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 };
+    int *q = __builtin_memchr (p, 2, 3 * sizeof *p);
+    sink (q);
+  }
+  {
+    int a[] = { i, i + 1, i + 2, 3 };
+    int *p = i ? (int[]){ j, j + 1, j + 2, 3 } : a;
+    int *q = __builtin_memchr (p, 3, 4 * sizeof *p);
+    sink (q);
+  }
+}
+
+
+void warn_maybe_compound_literal (int i, int j)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    p = i ? (int[]){ 4, 5, 6 } : a;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);         // { dg-warning "dangling pointer \('p' \)?to a compound literal may be used" }
+}
+
+
+void warn_maybe_compound_literal_memchr (int i, int j, int x)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int *q = i ? (int[]){ 4, 5, 6 } : a;
+    p = memchr (q, x, 3 * sizeof *q);
+  }
+  sink (p);         // { dg-warning "dangling pointer 'p' to a compound literal may be used" }
+}
+
+
+void warn_maybe_array (int i, int j)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int b[] = { 4, 5, 6 };
+    p = i ? a : b;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);         // { dg-warning "dangling pointer \('p' \)?to 'b' may be used" }
+}
+
+
+void warn_maybe_array_memchr (int i, int j, int x)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int b[] = { 4, 5, 6 };
+    int *q = i ? a : b;
+    p = memchr (q, x, 3 * sizeof *q);
+  }
+  sink (p);         // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer.c b/gcc/testsuite/gcc.dg/Wdangling-pointer.c
new file mode 100644
index 00000000000..e32ac7d7a46
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wdangling-pointer.c
@@ -0,0 +1,62 @@
+/* Exercise basic C-only cases of -Wdangling-pointer.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memchr (const void*, int, size_t);
+extern char* strchr (const char*, int);
+
+void sink (const void*, ...);
+
+
+void nowarn_compound_literal (int i)
+{
+  {
+    int *p = (int[]){ 1, 2, 3 };
+    sink (p);
+  }
+  {
+    int *q = (int[]){ 1, 2, 3 };
+    int *p = &q[1];
+    sink (p);
+  }
+  {
+    int *p = __builtin_memchr ((int[]){ 1, 2, 3 }, 2, 3 * sizeof *p);
+    sink (p);
+  }
+  {
+    int *p = __builtin_memchr ((int[]){ i, i + 1 }, 3, 2 * sizeof *p);
+    sink (p);
+  }
+}
+
+
+void warn_compound_literal (int i)
+{
+  int *p;
+  {
+    p = (int[]){ 1, 2, 3 };   // { dg-message "compound literal" },
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+
+  {
+    int *q =
+      (int[]){ 1, 2, 3 };     // { dg-message "compound literal" },
+    p = &q[1];
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+  {
+    p = (int*)memchr (
+	  (int[]){ 1, 2, 3 }, // { dg-message "compound literal" }
+	  2, 3 * sizeof *p);
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+
+  {
+    p = (int*)memchr (
+	  (int[]){ i, i + 1 },// { dg-message "compound literal" }
+	  3, 2 * sizeof *p);
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+}
diff --git a/gcc/testsuite/gcc.dg/uninit-pr50476.c b/gcc/testsuite/gcc.dg/uninit-pr50476.c
index db4146db314..37201841ad5 100644
--- a/gcc/testsuite/gcc.dg/uninit-pr50476.c
+++ b/gcc/testsuite/gcc.dg/uninit-pr50476.c
@@ -7,7 +7,7 @@ int *x = 0;
 void f (void)
 {
   int y = 1;
-  x = &y;
+  x = &y;       // { dg-warning "\\\[-Wdangling-pointer" }
 }
 
 int g (void)

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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
@ 2021-11-02  5:32   ` Eric Gallager
  2021-11-02 17:09     ` Martin Sebor
  2021-11-02 22:29   ` David Malcolm
  2021-11-23  1:32   ` Jeff Law
  2 siblings, 1 reply; 34+ messages in thread
From: Eric Gallager @ 2021-11-02  5:32 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Mon, Nov 1, 2021 at 6:18 PM Martin Sebor via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Patch 1 in the series detects a small subset of uses of pointers
> made indeterminate by calls to deallocation functions like free
> or C++ operator delete.  To control the conditions the warnings
> are issued under the new -Wuse-after-free= option provides three
> levels.  At the lowest level the warning triggers only for
> unconditional uses of freed pointers and doesn't warn for uses
> in equality expressions.  Level 2 warns also for come conditional
> uses, and level 3 also for uses in equality expressions.
>
> I debated whether to make level 2 or 3 the default included in
> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
> of both the problem and GCC's new ability to detect it: using
> a pointer after it's been freed, even only in principle, by
> a successful call to realloc, is undefined, and 2) because
> it's trivial to lower the level either globally, or locally
> by suppressing the warning around such misuses.
>
> I've tested the patch on x86_64-linux and by building Glibc
> and Binutils/GDB.  It triggers a number of times in each, all
> due to comparing invalidated pointers for equality (i.e., level
> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
> and will see how the Glibc folks want to deal with theirs (I
> track them in BZ #28521).
>
> The tests contain a number of xfails due to limitations I'm
> aware of.  I marked them pr?????? until the patch is approved.
> I will open bugs for them before committing if I don't resolve
> them in a followup.
>
> Martin

Hi, I'm just wondering how this fares compared to the static
analyzer's -Wanalyzer-use-after-free; could you compare and contrast
them for us?
Thanks,
Eric

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

* Re: [PATCH 2/2] add -Wdangling-pointer [PR #63272]
  2021-11-01 22:18 ` [PATCH 2/2] add -Wdangling-pointer [PR #63272] Martin Sebor
@ 2021-11-02  7:40   ` Eric Gallager
  2021-11-02 18:38     ` Martin Sebor
  2021-11-30 22:55   ` [PATCH v2 " Martin Sebor
  1 sibling, 1 reply; 34+ messages in thread
From: Eric Gallager @ 2021-11-02  7:40 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Martin Sebor, gcc-patches

On Mon, Nov 1, 2021 at 6:20 PM Martin Sebor via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Patch 2 in this series adds support for detecting the uses of
> dangling pointers: those to auto objects that have gone out of
> scope.  Like patch 1, to minimize false positives this detection
> is very simplistic.  However, thanks to the more deterministic
> nature of the problem (all local objects go out of scope) is able
> to detect more instances of it.  The approach I used is to simply
> search the IL for clobbers that dominate uses of pointers to
> the clobbered objects.  If such a use is found that's not
> followed by a clobber of the same object the warning triggers.
> Similar to -Wuse-after-free, the new -Wdangling-pointer option
> has multiple levels: level 1 to detect unconditional uses and
> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
> there is no use case for testing dangling pointers for
> equality, so there is no level 3.
>
> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
> It found no problems outside of the GCC test suite.
>
> As with the first patch in this series, the tests contain a number
> of xfails due to known limitations marked with pr??????.  I'll
> open bugs for them before committing the patch if I don't resolve
> them first in a followup.
>
> Martin

So, I'd just like to take this chance to re-state my preference (as a
user) for having separate named options for warnings instead of having
a single option with multiple levels, so that users can toggle just
one but not the other. With the numerical levels, one can detect only
unconditional uses, and not conditional ones, by using level one, but
they can't do it the other way around (i.e. detecting conditional
uses, but not unconditional ones), though. I think having a split like
the one that exists between -Wuninitialized and -Wmaybe-uninitialized
would make sense here.

Eric

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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-02  5:32   ` Eric Gallager
@ 2021-11-02 17:09     ` Martin Sebor
  0 siblings, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-02 17:09 UTC (permalink / raw)
  To: Eric Gallager; +Cc: gcc-patches

On 11/1/21 11:32 PM, Eric Gallager wrote:
> On Mon, Nov 1, 2021 at 6:18 PM Martin Sebor via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
>>
>> Patch 1 in the series detects a small subset of uses of pointers
>> made indeterminate by calls to deallocation functions like free
>> or C++ operator delete.  To control the conditions the warnings
>> are issued under the new -Wuse-after-free= option provides three
>> levels.  At the lowest level the warning triggers only for
>> unconditional uses of freed pointers and doesn't warn for uses
>> in equality expressions.  Level 2 warns also for come conditional
>> uses, and level 3 also for uses in equality expressions.
>>
>> I debated whether to make level 2 or 3 the default included in
>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>> of both the problem and GCC's new ability to detect it: using
>> a pointer after it's been freed, even only in principle, by
>> a successful call to realloc, is undefined, and 2) because
>> it's trivial to lower the level either globally, or locally
>> by suppressing the warning around such misuses.
>>
>> I've tested the patch on x86_64-linux and by building Glibc
>> and Binutils/GDB.  It triggers a number of times in each, all
>> due to comparing invalidated pointers for equality (i.e., level
>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>> and will see how the Glibc folks want to deal with theirs (I
>> track them in BZ #28521).
>>
>> The tests contain a number of xfails due to limitations I'm
>> aware of.  I marked them pr?????? until the patch is approved.
>> I will open bugs for them before committing if I don't resolve
>> them in a followup.
>>
>> Martin
> 
> Hi, I'm just wondering how this fares compared to the static
> analyzer's -Wanalyzer-use-after-free; could you compare and contrast
> them for us?

Good question.

The analyzer does a far more exhaustive, interprocedural
analysis of (most) paths through a program, symbolically
evaluating the conditions under which statements are
evaluated to determine reachability.

This initial implementation of -Wuse-after-free does only
a superficial analysis of a few nearby statements in a single
function (plus those inlined into it), those with direct
dependencies of uses on the deallocation statements.  It
doesn't do any evaluation of conditions which limits how
far it can go in its checking.  If it sees a pointer used
after a free call as in

   free (p);
   return *p;   // used after free (level 1)

it triggers.  If it sees a conditional use as in

   free (p);
   if (cond)
     return *p;   // may be used after free (level 2)

it triggers at level 2, but only if the free is unconditional
and flows directly into the condition guarding the use.  If
the free is guarded by another condition it doesn't trigger:

   if (c_1)
     free (p);

   if (c_2)
     return *p;

This last case is the consequence of not doing any condition
evaluation (c_1 could be mutually exclusive with c_2).  Adding
support for it is a future enhancement, something I'm out of
time for in this stage 1 but I'd like to tackle for GCC 13.
Both GCC's and Clang's analyzers detect all three cases.

Unlike GCC's analyzer (but like Clang's), this new warning
flags all pointer uses, not just their derefernces (operands
of equality expressions only at level 3; Clang doesn't seem
to diagnose uses in equality tests at all).  So unlike
GCC's analyzer, it will trigger in the first two example
above even if it's the pointer itself being returned and not
what it pointed to before it was freed .  This is because
a freed pointer is invalid the same way an uninitialized
variable is invalid, and using it in any way is strictly
undefined  (returning it from a function or passing as
an argument to another can lead to memory corruption or
unintentional information disclosure).

Martin

PS The distinction between levels 1 (unconditional uses)
and 2 (conditional) is more like that between the levels
of -Warray-bounds -- level 2 catching slightly more cases
at the expense of potentially some, albeit rare, false
positives.  It's not like between -Wuninitialized and
-Wmaybe-uninitialized where the uninitialized checker
employs limited symbolic predicate analysis and the need
for separate options came out of the desire to be able to
control separately the false positives due to
the imperfections inherent in this strategy (or incidental
to its implementation in GCC: the limited ability to
determine that two conditions are mutually exclusive.

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

* Re: [PATCH 2/2] add -Wdangling-pointer [PR #63272]
  2021-11-02  7:40   ` Eric Gallager
@ 2021-11-02 18:38     ` Martin Sebor
  0 siblings, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-02 18:38 UTC (permalink / raw)
  To: Eric Gallager, Martin Sebor; +Cc: gcc-patches

On 11/2/21 1:40 AM, Eric Gallager wrote:
> On Mon, Nov 1, 2021 at 6:20 PM Martin Sebor via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
>>
>> Patch 2 in this series adds support for detecting the uses of
>> dangling pointers: those to auto objects that have gone out of
>> scope.  Like patch 1, to minimize false positives this detection
>> is very simplistic.  However, thanks to the more deterministic
>> nature of the problem (all local objects go out of scope) is able
>> to detect more instances of it.  The approach I used is to simply
>> search the IL for clobbers that dominate uses of pointers to
>> the clobbered objects.  If such a use is found that's not
>> followed by a clobber of the same object the warning triggers.
>> Similar to -Wuse-after-free, the new -Wdangling-pointer option
>> has multiple levels: level 1 to detect unconditional uses and
>> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
>> there is no use case for testing dangling pointers for
>> equality, so there is no level 3.
>>
>> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
>> It found no problems outside of the GCC test suite.
>>
>> As with the first patch in this series, the tests contain a number
>> of xfails due to known limitations marked with pr??????.  I'll
>> open bugs for them before committing the patch if I don't resolve
>> them first in a followup.
>>
>> Martin
> 
> So, I'd just like to take this chance to re-state my preference (as a
> user) for having separate named options for warnings instead of having
> a single option with multiple levels, so that users can toggle just
> one but not the other. With the numerical levels, one can detect only
> unconditional uses, and not conditional ones, by using level one, but
> they can't do it the other way around (i.e. detecting conditional
> uses, but not unconditional ones), though. I think having a split like
> the one that exists between -Wuninitialized and -Wmaybe-uninitialized
> would make sense here.

I agree that separate options are preferable for warnings with
different design strategies.  That said, I can't think of a use
case for enabling the "higher" level of either of any of our
warnings without also enabling the former.  Their sole purpose
is to control the S/R ratio, or perhaps also the cost in terms
of the compile-time resources spent on the analysis, though
I'm not aware of any measurements backing this up.

The distinction between levels 1 and 2 in the proposed warnings
is fairly subtle, and the higher level is by design exceedingly
unlikely to result in enough false positives to make lowering
it worthwhile.  It may not even be worth exposing a difference
between them via an option.   In fact, I think it might make
sense to merge them (perhaps keeping the "may" phrasing) unless
a compelling argument turns up in favor of keeping them distinct.

Martin

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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
  2021-11-02  5:32   ` Eric Gallager
@ 2021-11-02 22:29   ` David Malcolm
  2021-11-03  0:22     ` Martin Sebor
  2021-11-23  1:32   ` Jeff Law
  2 siblings, 1 reply; 34+ messages in thread
From: David Malcolm @ 2021-11-02 22:29 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Mon, 2021-11-01 at 16:17 -0600, Martin Sebor via Gcc-patches wrote:
> Patch 1 in the series detects a small subset of uses of pointers
> made indeterminate by calls to deallocation functions like free
> or C++ operator delete.  To control the conditions the warnings
> are issued under the new -Wuse-after-free= option provides three
> levels.  At the lowest level the warning triggers only for
> unconditional uses of freed pointers and doesn't warn for uses
> in equality expressions.  Level 2 warns also for come conditional
> uses, and level 3 also for uses in equality expressions.
> 
> I debated whether to make level 2 or 3 the default included in
> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
> of both the problem and GCC's new ability to detect it: using
> a pointer after it's been freed, even only in principle, by
> a successful call to realloc, is undefined, and 2) because
> it's trivial to lower the level either globally, or locally
> by suppressing the warning around such misuses.
> 
> I've tested the patch on x86_64-linux and by building Glibc
> and Binutils/GDB.  It triggers a number of times in each, all
> due to comparing invalidated pointers for equality (i.e., level
> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
> and will see how the Glibc folks want to deal with theirs (I
> track them in BZ #28521).

For reference, this is:
  https://sourceware.org/bugzilla/show_bug.cgi?id=28521

> 
> The tests contain a number of xfails due to limitations I'm
> aware of.  I marked them pr?????? until the patch is approved.
> I will open bugs for them before committing if I don't resolve
> them in a followup.
> 

[...snip...]

> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index c5730228821..eb4ecb56dcc 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -4341,6 +4341,60 @@ annotations.
>  Warn about overriding virtual functions that are not marked with the
>  @code{override} keyword.
>  
> +@item -Wuse-after-free
> +@itemx -Wuse-after-free=@var{n}
> +@opindex Wuse-after-free
> +@opindex Wno-use-after-free
> +Warn about uses of pointers to dynamically allocated objects that
have
> +been rendered indeterminate by a call to a deallocation function.
> +
> +@table @gcctabopt
> +@item -Wuse-after-free=1
> +At level 1 the warning attempts to diagnose only unconditional uses
of
> +pointers made indeterminate by a deallocation call.  This includes
> +double-@code{free} calls.  Although undefined, uses of indeterminate
> +pointers in equality (or inequality) expressions are not diagnosed
at
> +this level.
> +@item -Wuse-after-free=2
> +At level 2, in addition to unconditional uses the warning also
diagnoses
> +conditional uses of pointers made indeterminate by a deallocation
call.
> +As at level 1, uses in (or inequality) equality expressions are not
> +diagnosed.  For example, the second call to @code{free} in the
following
> +function is diagnosed at this level:
> +@smallexample
> +struct A @{ int refcount; void *data; @};
> +
> +void release (struct A *p)
> +@{
> +  int refcount = --p->refcount;
> +  free (p);
> +  if (refcount == 0)
> +    free (p->data);   // warning: p may be used after free
> +@}
> +@end smallexample
> +@item -Wuse-after-free=3
> +At level 3, the warning also diagnoses uses of indeterminate
pointers in
> +equality expressions.  All uses of indeterminate pointers are
undefined
> +but equality tests sometimes appear after calls to @code{realloc} as
> +an attempt to determine whether the call resulted in relocating the
object
> +to a different address.  They are diagnosed at a separate level to
aid
> +legacy code gradually transition to safe alternatives.  For example,
> +the equality test in the function below is diagnosed at this level:
> +@smallexample
> +void adjust_pointers (int**, int);
> +
> +void grow (int **p, int n)
> +@{
> +  int **q = (int**)realloc (p, n *= 2);
> +  if (q == p)
> +    return;
> +  adjust_pointers ((int**)q, n);
> +@}
> +@end smallexample
> +@end table
> +
> +@option{-Wuse-after-free=3} is included in @option{-Wall}.

Recapping our chat earlier today, I confess to not being familiar with
this aspect of the C standard, but IIRC you were saying that the
pointer passed in to "realloc" is always "indeterminate" after a
successful call, and indeed I see on:
  https://en.cppreference.com/w/c/memory/realloc
"The original pointer ptr is invalidated and any access to it is
undefined behavior (even if reallocation was in-place)."

Does this really mean that it's undefined to use the original "p" after
an in-place reallocation (as opposed to "*p"?).  I find this
surprising, and I think many of our users would too.

If that's the case, is there a standards-conforming way for code to
distinguish between a successful in-place vs a successful moving
realloc?  How would a user "transition [their code] to safe
alternatives"?  (the docs should probably spell that out).  i.e. what's
an idiomatic way for the user to write this logic correctly?

How is this behavior unsafe, or how could it become unsafe?  If it's
merely a theoretical concern, I think level 2 would be better for -Wall
(or the default).

Have you seen level 3 catch anything that *isn't* a usage of realloc
checking success for in-place resizing vs moving?

[...snip...]

Hope this is constructive; sorry about my ignorance here.
Dave



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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-02 22:29   ` David Malcolm
@ 2021-11-03  0:22     ` Martin Sebor
  0 siblings, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-03  0:22 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/2/21 4:29 PM, David Malcolm wrote:
> On Mon, 2021-11-01 at 16:17 -0600, Martin Sebor via Gcc-patches wrote:
>> Patch 1 in the series detects a small subset of uses of pointers
>> made indeterminate by calls to deallocation functions like free
>> or C++ operator delete.  To control the conditions the warnings
>> are issued under the new -Wuse-after-free= option provides three
>> levels.  At the lowest level the warning triggers only for
>> unconditional uses of freed pointers and doesn't warn for uses
>> in equality expressions.  Level 2 warns also for come conditional
>> uses, and level 3 also for uses in equality expressions.
>>
>> I debated whether to make level 2 or 3 the default included in
>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>> of both the problem and GCC's new ability to detect it: using
>> a pointer after it's been freed, even only in principle, by
>> a successful call to realloc, is undefined, and 2) because
>> it's trivial to lower the level either globally, or locally
>> by suppressing the warning around such misuses.
>>
>> I've tested the patch on x86_64-linux and by building Glibc
>> and Binutils/GDB.  It triggers a number of times in each, all
>> due to comparing invalidated pointers for equality (i.e., level
>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>> and will see how the Glibc folks want to deal with theirs (I
>> track them in BZ #28521).
> 
> For reference, this is:
>    https://sourceware.org/bugzilla/show_bug.cgi?id=28521
> 
>>
>> The tests contain a number of xfails due to limitations I'm
>> aware of.  I marked them pr?????? until the patch is approved.
>> I will open bugs for them before committing if I don't resolve
>> them in a followup.
>>
> 
> [...snip...]
> 
>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
>> index c5730228821..eb4ecb56dcc 100644
>> --- a/gcc/doc/invoke.texi
>> +++ b/gcc/doc/invoke.texi
>> @@ -4341,6 +4341,60 @@ annotations.
>>   Warn about overriding virtual functions that are not marked with the
>>   @code{override} keyword.
>>   
>> +@item -Wuse-after-free
>> +@itemx -Wuse-after-free=@var{n}
>> +@opindex Wuse-after-free
>> +@opindex Wno-use-after-free
>> +Warn about uses of pointers to dynamically allocated objects that
> have
>> +been rendered indeterminate by a call to a deallocation function.
>> +
>> +@table @gcctabopt
>> +@item -Wuse-after-free=1
>> +At level 1 the warning attempts to diagnose only unconditional uses
> of
>> +pointers made indeterminate by a deallocation call.  This includes
>> +double-@code{free} calls.  Although undefined, uses of indeterminate
>> +pointers in equality (or inequality) expressions are not diagnosed
> at
>> +this level.
>> +@item -Wuse-after-free=2
>> +At level 2, in addition to unconditional uses the warning also
> diagnoses
>> +conditional uses of pointers made indeterminate by a deallocation
> call.
>> +As at level 1, uses in (or inequality) equality expressions are not
>> +diagnosed.  For example, the second call to @code{free} in the
> following
>> +function is diagnosed at this level:
>> +@smallexample
>> +struct A @{ int refcount; void *data; @};
>> +
>> +void release (struct A *p)
>> +@{
>> +  int refcount = --p->refcount;
>> +  free (p);
>> +  if (refcount == 0)
>> +    free (p->data);   // warning: p may be used after free
>> +@}
>> +@end smallexample
>> +@item -Wuse-after-free=3
>> +At level 3, the warning also diagnoses uses of indeterminate
> pointers in
>> +equality expressions.  All uses of indeterminate pointers are
> undefined
>> +but equality tests sometimes appear after calls to @code{realloc} as
>> +an attempt to determine whether the call resulted in relocating the
> object
>> +to a different address.  They are diagnosed at a separate level to
> aid
>> +legacy code gradually transition to safe alternatives.  For example,
>> +the equality test in the function below is diagnosed at this level:
>> +@smallexample
>> +void adjust_pointers (int**, int);
>> +
>> +void grow (int **p, int n)
>> +@{
>> +  int **q = (int**)realloc (p, n *= 2);
>> +  if (q == p)
>> +    return;
>> +  adjust_pointers ((int**)q, n);
>> +@}
>> +@end smallexample
>> +@end table
>> +
>> +@option{-Wuse-after-free=3} is included in @option{-Wall}.
> 
> Recapping our chat earlier today, I confess to not being familiar with
> this aspect of the C standard, but IIRC you were saying that the
> pointer passed in to "realloc" is always "indeterminate" after a
> successful call, and indeed I see on:
>    https://en.cppreference.com/w/c/memory/realloc
> "The original pointer ptr is invalidated and any access to it is
> undefined behavior (even if reallocation was in-place)."
> 
> Does this really mean that it's undefined to use the original "p" after
> an in-place reallocation (as opposed to "*p"?).  I find this
> surprising, and I think many of our users would too.

Yes, it's undefined to use the pointer after a successful
reallocation (it's okay to use it after a failed one, although
as discussed in WG14 DR400, even that can be problematic due
to a lack of clarity in the standards).

One rationale for it is to make it possible to implement realloc
with no in-place extension, as a sequence of three calls: malloc,
memcpy, and free.  Another is that using invalidated pointers is
inherently unsafe and the C committee wanted to leave room for
debugging hardware that traps on the use of such pointers
(US7966480 describes one such solution).  Such solutions are
only feasible when all invalid pointers are treated the same.

> If that's the case, is there a standards-conforming way for code to
> distinguish between a successful in-place vs a successful moving
> realloc?  How would a user "transition [their code] to safe
> alternatives"?  (the docs should probably spell that out).  i.e. what's
> an idiomatic way for the user to write this logic correctly?

There's no portable mechanism to detect whether a realloc call
moved an object.  Strictly conforming programs must avoid it.
The idiomatic way of avoiding it is to store offsets rather
than pointers to reallocated memory.

> How is this behavior unsafe, or how could it become unsafe?  If it's
> merely a theoretical concern, I think level 2 would be better for -Wall
> (or the default).

It's difficult to quantify how unsafe this or that undefined
construct is.  I think it's probably comparable to passing null
pointers and zero size to memcpy (diagnosed by -Wnonnull, also
in -Wall).  Or to passing T* to %p in a call to printf where T
isn't void (diagnosed by -Wformat, in -Wall).  Or to [ab]using
uninitialized variables as a source of entropy.

It's a judgment call what checkers to include at what level of
what option.  I made this choice not just because it's undefined
but also because it could help find mistakes in nearby code (as
in the Binutils case below or ideally more impactful).  Because
it's little known I'm hoping even the benign instances will help
raise awareness of the issue.  But I made it a separate level
to make it easy for users to suppres, and for us to adjust if
necessary.

> 
> Have you seen level 3 catch anything that *isn't* a usage of realloc
> checking success for in-place resizing vs moving?

The Glibc use in ldconfig.c is a use after free.  But it's just
sloppy coding, not a "real" bug.  It also found one instance in
Binutils (libctf/ctf-open-bfd.c).  That one is also not really
a bug, more like a thinko:

       isymbuf = bfd_elf_get_elf_syms (abfd, symhdr, symcount, 0,
				      NULL, symtab, NULL);
       free (isymbuf);
       if (isymbuf == NULL)
	{
	  bfderrstr = N_("cannot read symbol table");
	  goto err_free_sym;
	}

The call to free should follow the if statement.

I expect it to find plenty more benign misues like this in other
code, but what I'm really hoping of course is that it will help
find actual bugs, even if indirectly.

> 
> [...snip...]
> 
> Hope this is constructive; sorry about my ignorance here.
> Dave

Sure.

Martin

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

* PING [PATCH 0/2] provide simple detection of indeterminate pointers
  2021-11-01 22:15 [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
  2021-11-01 22:18 ` [PATCH 2/2] add -Wdangling-pointer [PR #63272] Martin Sebor
@ 2021-11-08 22:41 ` Martin Sebor
  2021-11-15 16:47   ` PING 2 " Martin Sebor
  2 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-11-08 22:41 UTC (permalink / raw)
  To: gcc-patches

Ping for the two patches below:

-Wuse-after-free:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html

and -Wdangling-pointer:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html

On 11/1/21 4:15 PM, Martin Sebor wrote:
> This two-patch series adds support for the detection of uses
> of pointers invalidated as a result of the lifetime of
> the objects they point to having ended: either explicitly,
> after a call to a dynamic deallocation function, or implicitly,
> by virtue of an object with automatic storage duration having
> gone out of scope.
> 
> To minimize false positives the initial logic is very simple
> (even simplistic): the code only checks uses in basic blocks
> dominated by the invalidating calls (either calls to
> deallocation functions or GCC's clobbers).
> 
> A more thorough checker is certainly possible and I'd say most
> desirable but will require a more sophisticated implementation
> and a better predicate analyzer than is available, and so will
> need to wait for GCC 13.
> 
> Martin


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

* PING 2 [PATCH 0/2] provide simple detection of indeterminate pointers
  2021-11-08 22:41 ` PING [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
@ 2021-11-15 16:47   ` Martin Sebor
  2021-11-22 16:41     ` PING 3 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-11-15 16:47 UTC (permalink / raw)
  To: gcc-patches

Pinging the two patches below:

-Wuse-after-free:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html

and -Wdangling-pointer:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html

On 11/8/21 3:41 PM, Martin Sebor wrote:
> Ping for the two patches below:
> 
> -Wuse-after-free:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html
> 
> and -Wdangling-pointer:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html
> 
> On 11/1/21 4:15 PM, Martin Sebor wrote:
>> This two-patch series adds support for the detection of uses
>> of pointers invalidated as a result of the lifetime of
>> the objects they point to having ended: either explicitly,
>> after a call to a dynamic deallocation function, or implicitly,
>> by virtue of an object with automatic storage duration having
>> gone out of scope.
>>
>> To minimize false positives the initial logic is very simple
>> (even simplistic): the code only checks uses in basic blocks
>> dominated by the invalidating calls (either calls to
>> deallocation functions or GCC's clobbers).
>>
>> A more thorough checker is certainly possible and I'd say most
>> desirable but will require a more sophisticated implementation
>> and a better predicate analyzer than is available, and so will
>> need to wait for GCC 13.
>>
>> Martin
> 


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

* PING 3 [PATCH 0/2] provide simple detection of indeterminate pointers
  2021-11-15 16:47   ` PING 2 " Martin Sebor
@ 2021-11-22 16:41     ` Martin Sebor
  0 siblings, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-22 16:41 UTC (permalink / raw)
  To: gcc-patches

Pinging the two patches below:

-Wuse-after-free:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html

and -Wdangling-pointer:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html

On 11/15/21 9:47 AM, Martin Sebor wrote:
> Pinging the two patches below:
> 
> -Wuse-after-free:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html
> 
> and -Wdangling-pointer:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html
> 
> On 11/8/21 3:41 PM, Martin Sebor wrote:
>> Ping for the two patches below:
>>
>> -Wuse-after-free:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583044.html
>>
>> and -Wdangling-pointer:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583045.html
>>
>> On 11/1/21 4:15 PM, Martin Sebor wrote:
>>> This two-patch series adds support for the detection of uses
>>> of pointers invalidated as a result of the lifetime of
>>> the objects they point to having ended: either explicitly,
>>> after a call to a dynamic deallocation function, or implicitly,
>>> by virtue of an object with automatic storage duration having
>>> gone out of scope.
>>>
>>> To minimize false positives the initial logic is very simple
>>> (even simplistic): the code only checks uses in basic blocks
>>> dominated by the invalidating calls (either calls to
>>> deallocation functions or GCC's clobbers).
>>>
>>> A more thorough checker is certainly possible and I'd say most
>>> desirable but will require a more sophisticated implementation
>>> and a better predicate analyzer than is available, and so will
>>> need to wait for GCC 13.
>>>
>>> Martin
>>
> 


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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
  2021-11-02  5:32   ` Eric Gallager
  2021-11-02 22:29   ` David Malcolm
@ 2021-11-23  1:32   ` Jeff Law
  2021-11-23 21:16     ` Martin Sebor
  2 siblings, 1 reply; 34+ messages in thread
From: Jeff Law @ 2021-11-23  1:32 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches



On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
> Patch 1 in the series detects a small subset of uses of pointers
> made indeterminate by calls to deallocation functions like free
> or C++ operator delete.  To control the conditions the warnings
> are issued under the new -Wuse-after-free= option provides three
> levels.  At the lowest level the warning triggers only for
> unconditional uses of freed pointers and doesn't warn for uses
> in equality expressions.  Level 2 warns also for come conditional
> uses, and level 3 also for uses in equality expressions.
>
> I debated whether to make level 2 or 3 the default included in
> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
> of both the problem and GCC's new ability to detect it: using
> a pointer after it's been freed, even only in principle, by
> a successful call to realloc, is undefined, and 2) because
> it's trivial to lower the level either globally, or locally
> by suppressing the warning around such misuses.
>
> I've tested the patch on x86_64-linux and by building Glibc
> and Binutils/GDB.  It triggers a number of times in each, all
> due to comparing invalidated pointers for equality (i.e., level
> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
> and will see how the Glibc folks want to deal with theirs (I
> track them in BZ #28521).
>
> The tests contain a number of xfails due to limitations I'm
> aware of.  I marked them pr?????? until the patch is approved.
> I will open bugs for them before committing if I don't resolve
> them in a followup.
>
> Martin
>
> gcc-63272-1.diff
>
> Add -Wuse-after-free.
>
> gcc/c-family/ChangeLog
>
> 	* c.opt (-Wuse-after-free): New options.
>
> gcc/ChangeLog:
>
> 	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
> 	OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
> 	* diagnostic-spec.h (NW_DANGLING): New enumerator.
> 	* doc/invoke.texi (-Wuse-after-free): Document new option.
> 	* gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
> 	(pass_waccess::check_call_access): ...to this.
> 	(pass_waccess::check): Rename...
> 	(pass_waccess::check_block): ...to this.
> 	(pass_waccess::check_pointer_uses): New function.
> 	(pass_waccess::gimple_call_return_arg): New function.
> 	(pass_waccess::warn_invalid_pointer): New function.
> 	(pass_waccess::check_builtin): Handle free and realloc.
> 	(gimple_use_after_inval_p): New function.
> 	(get_realloc_lhs): New function.
> 	(maybe_warn_mismatched_realloc): New function.
> 	(pointers_related_p): New function.
> 	(pass_waccess::check_call): Call check_pointer_uses.
> 	(pass_waccess::execute): Compute and free dominance info.
>
> libcpp/ChangeLog:
>
> 	* files.c (_cpp_find_file): Substitute a valid pointer for
> 	an invalid one to avoid -Wuse-0after-free.
>
> libiberty/ChangeLog:
>
> 	* regex.c: Suppress -Wuse-after-free.
>
> gcc/testsuite/ChangeLog:
>
> 	* gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
> 	* gcc.dg/Wmismatched-dealloc-3.c: Same.
> 	* gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
> 	* gcc.dg/attr-alloc_size-7.c: Same.
> 	* c-c++-common/Wuse-after-free-2.c: New test.
> 	* c-c++-common/Wuse-after-free-3.c: New test.
> 	* c-c++-common/Wuse-after-free-4.c: New test.
> 	* c-c++-common/Wuse-after-free-5.c: New test.
> 	* c-c++-common/Wuse-after-free-6.c: New test.
> 	* c-c++-common/Wuse-after-free-7.c: New test.
> 	* c-c++-common/Wuse-after-free.c: New test.
> 	* g++.dg/warn/Wdangling-pointer.C: New test.
> 	* g++.dg/warn/Wmismatched-dealloc-3.C: New test.
> 	* g++.dg/warn/Wuse-after-free.C: New test.
>
> diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
> index 63fc27a1487..2065402a2b9 100644
> --- a/gcc/gimple-ssa-warn-access.cc
> +++ b/gcc/gimple-ssa-warn-access.cc
>
> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call (gcall *call)
>       }
>   }
>   
> +/* Return true if either USE_STMT's basic block (that of a pointer's use)
> +   is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
> +   which is either a clobber or a deallocation call), or if they're in
> +   the same block, USE_STMT follows INVAL_STMT.  */
> +
> +static bool
> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
> +			  bool last_block = false)
> +{
> +  tree clobvar =
> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE;
> +
> +  basic_block inval_bb = gimple_bb (inval_stmt);
> +  basic_block use_bb = gimple_bb (use_stmt);
> +
> +  if (inval_bb != use_bb)
> +    {
> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
> +	return true;
> +
> +      if (!clobvar || !last_block)
> +	return false;
> +
> +      auto gsi = gsi_for_stmt (use_stmt);
> +
> +      auto_bitmap visited;
> +
> +      /* A use statement in the last basic block in a function or one that
> +	 falls through to it is after any other prior clobber of the used
> +	 variable unless it's followed by a clobber of the same variable. */
> +      basic_block bb = use_bb;
> +      while (bb != inval_bb
> +	     && single_succ_p (bb)
> +	     && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
> +	{
> +	  if (!bitmap_set_bit (visited, bb->index))
> +	    /* Avoid cycles. */
> +	    return true;
> +
> +	  for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
> +	    {
> +	      gimple *stmt = gsi_stmt (gsi);
> +	      if (gimple_clobber_p (stmt))
> +		{
> +		  if (clobvar == gimple_assign_lhs (stmt))
> +		    /* The use is followed by a clobber.  */
> +		    return false;
> +		}
> +	    }
> +
> +	  bb = single_succ (bb);
> +	  gsi = gsi_start_bb (bb);
> +	}
> +
> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
> +    }
?!?  I would have thought the block dominance test plus checking UIDs if 
the two statements are in the same block would be all you need.  Can you 
elaborate more on what that hunk above is trying to do?


> +
> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
> +       gsi_next_nondebug (&si))
> +    {
> +      gimple *stmt = gsi_stmt (si);
> +      if (stmt == use_stmt)
> +	return true;
> +    }
> +
> +  return false;
> +}
So from a compile-time standpoint, would it be better to to assign UIDs 
to each statement so that within a block you can just compare the UIDs?  
That's a pretty standard way to deal with the problem of statement 
domination within a block if we're going to be doing multiple queries.
>
> +
> +/* Return true if P and Q point to the same object, and false if they
> +   either don't or their relationship cannot be determined.  */
> +
> +static bool
> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
> +{
> +  if (!ptr_derefs_may_alias_p (p, q))
> +    return false;
Hmm, I guess that you don't need to worry about the case where P and Q 
point to different elements within an array.  They point to different 
final objects, though they do share a common enclosing object.  
Similarly for P & Q pointing to different members within a structure.
> +
> +/* For a STMT either a call to a deallocation function or a clobber, warn
> +   for uses of the pointer PTR it was called with (including its copies
> +   or others derived from it by pointer arithmetic).  */
> +
> +void
> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
> +{
> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
> +
> +  const bool check_dangling = !is_gimple_call (stmt);
> +  basic_block stmt_bb = gimple_bb (stmt);
> +
> +  /* If the deallocation (or clobber) statement dominates more than
> +     a single basic block issue a "maybe" k
That seems wrong.   What you're looking for is a post-dominance 
relationship I think.   If the sink (free/delete) is post-dominated by 
the use, then it's a "must", if it's not post-dominated, then it's a 
maybe.  Of course, that means you need to build post-dominators.
> +
> +	  if (check_dangling
> +	      && gimple_code (use_stmt) == GIMPLE_RETURN
> +	      && optimize && flag_isolate_erroneous_paths_dereference)
> +	    /* Avoid interfering with -Wreturn-local-addr (which runs only
> +	       with optimization enabled).  */
> +	    continue;
Umm, that looks like a hack.  I can't think of a good reason why removal 
of erroneous paths should gate any of this code.  ISTM that you're 
likely papering over a problem elsewhere.


Jeff

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

* Re: [PATCH 1/2] add -Wuse-after-free
  2021-11-23  1:32   ` Jeff Law
@ 2021-11-23 21:16     ` Martin Sebor
  2021-11-30 22:32       ` [PATCH v2 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-11-23 21:16 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

On 11/22/21 6:32 PM, Jeff Law wrote:
> 
> 
> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>> Patch 1 in the series detects a small subset of uses of pointers
>> made indeterminate by calls to deallocation functions like free
>> or C++ operator delete.  To control the conditions the warnings
>> are issued under the new -Wuse-after-free= option provides three
>> levels.  At the lowest level the warning triggers only for
>> unconditional uses of freed pointers and doesn't warn for uses
>> in equality expressions.  Level 2 warns also for come conditional
>> uses, and level 3 also for uses in equality expressions.
>>
>> I debated whether to make level 2 or 3 the default included in
>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>> of both the problem and GCC's new ability to detect it: using
>> a pointer after it's been freed, even only in principle, by
>> a successful call to realloc, is undefined, and 2) because
>> it's trivial to lower the level either globally, or locally
>> by suppressing the warning around such misuses.
>>
>> I've tested the patch on x86_64-linux and by building Glibc
>> and Binutils/GDB.  It triggers a number of times in each, all
>> due to comparing invalidated pointers for equality (i.e., level
>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>> and will see how the Glibc folks want to deal with theirs (I
>> track them in BZ #28521).
>>
>> The tests contain a number of xfails due to limitations I'm
>> aware of.  I marked them pr?????? until the patch is approved.
>> I will open bugs for them before committing if I don't resolve
>> them in a followup.
>>
>> Martin
>>
>> gcc-63272-1.diff
>>
>> Add -Wuse-after-free.
>>
>> gcc/c-family/ChangeLog
>>
>> 	* c.opt (-Wuse-after-free): New options.
>>
>> gcc/ChangeLog:
>>
>> 	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>> 	OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>> 	* diagnostic-spec.h (NW_DANGLING): New enumerator.
>> 	* doc/invoke.texi (-Wuse-after-free): Document new option.
>> 	* gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>> 	(pass_waccess::check_call_access): ...to this.
>> 	(pass_waccess::check): Rename...
>> 	(pass_waccess::check_block): ...to this.
>> 	(pass_waccess::check_pointer_uses): New function.
>> 	(pass_waccess::gimple_call_return_arg): New function.
>> 	(pass_waccess::warn_invalid_pointer): New function.
>> 	(pass_waccess::check_builtin): Handle free and realloc.
>> 	(gimple_use_after_inval_p): New function.
>> 	(get_realloc_lhs): New function.
>> 	(maybe_warn_mismatched_realloc): New function.
>> 	(pointers_related_p): New function.
>> 	(pass_waccess::check_call): Call check_pointer_uses.
>> 	(pass_waccess::execute): Compute and free dominance info.
>>
>> libcpp/ChangeLog:
>>
>> 	* files.c (_cpp_find_file): Substitute a valid pointer for
>> 	an invalid one to avoid -Wuse-0after-free.
>>
>> libiberty/ChangeLog:
>>
>> 	* regex.c: Suppress -Wuse-after-free.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>> 	* gcc.dg/Wmismatched-dealloc-3.c: Same.
>> 	* gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>> 	* gcc.dg/attr-alloc_size-7.c: Same.
>> 	* c-c++-common/Wuse-after-free-2.c: New test.
>> 	* c-c++-common/Wuse-after-free-3.c: New test.
>> 	* c-c++-common/Wuse-after-free-4.c: New test.
>> 	* c-c++-common/Wuse-after-free-5.c: New test.
>> 	* c-c++-common/Wuse-after-free-6.c: New test.
>> 	* c-c++-common/Wuse-after-free-7.c: New test.
>> 	* c-c++-common/Wuse-after-free.c: New test.
>> 	* g++.dg/warn/Wdangling-pointer.C: New test.
>> 	* g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>> 	* g++.dg/warn/Wuse-after-free.C: New test.
>>
>> diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
>> index 63fc27a1487..2065402a2b9 100644
>> --- a/gcc/gimple-ssa-warn-access.cc
>> +++ b/gcc/gimple-ssa-warn-access.cc
>>
>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call (gcall *call)
>>       }
>>   }
>>   
>> +/* Return true if either USE_STMT's basic block (that of a pointer's use)
>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
>> +   which is either a clobber or a deallocation call), or if they're in
>> +   the same block, USE_STMT follows INVAL_STMT.  */
>> +
>> +static bool
>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>> +			  bool last_block = false)
>> +{
>> +  tree clobvar =
>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE;
>> +
>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>> +  basic_block use_bb = gimple_bb (use_stmt);
>> +
>> +  if (inval_bb != use_bb)
>> +    {
>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>> +	return true;
>> +
>> +      if (!clobvar || !last_block)
>> +	return false;
>> +
>> +      auto gsi = gsi_for_stmt (use_stmt);
>> +
>> +      auto_bitmap visited;
>> +
>> +      /* A use statement in the last basic block in a function or one that
>> +	 falls through to it is after any other prior clobber of the used
>> +	 variable unless it's followed by a clobber of the same variable. */
>> +      basic_block bb = use_bb;
>> +      while (bb != inval_bb
>> +	     && single_succ_p (bb)
>> +	     && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
>> +	{
>> +	  if (!bitmap_set_bit (visited, bb->index))
>> +	    /* Avoid cycles. */
>> +	    return true;
>> +
>> +	  for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>> +	    {
>> +	      gimple *stmt = gsi_stmt (gsi);
>> +	      if (gimple_clobber_p (stmt))
>> +		{
>> +		  if (clobvar == gimple_assign_lhs (stmt))
>> +		    /* The use is followed by a clobber.  */
>> +		    return false;
>> +		}
>> +	    }
>> +
>> +	  bb = single_succ (bb);
>> +	  gsi = gsi_start_bb (bb);
>> +	}
>> +
>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>> +    }
> ?!?  I would have thought the block dominance test plus checking UIDs if 
> the two statements are in the same block would be all you need.  Can you 
> elaborate more on what that hunk above is trying to do?

The loop is entered only for -Wdangling-pointer.  It looks for
the first clobber of the CLOBVAR variable (one whose clobber
statement has been seen during the CFG traversal and whose use
is being validated) in the successors along the single edge
from the use block.  If the search finds a clobber, the use
is valid.  If it doesn't, the use is one of a variable having
gone out of scope (the clobber must be before the use).

Among the cases the loop handles is the one in PR 63272
(the request for -Wdangling-pointer) where the use neither
follows the clobber in the same block nor dominated by it.

There may be a way to optimize it somehow but because it's
a search I don't think a simple UID check alone would be
enough.

>> +
>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>> +       gsi_next_nondebug (&si))
>> +    {
>> +      gimple *stmt = gsi_stmt (si);
>> +      if (stmt == use_stmt)
>> +	return true;
>> +    }
>> +
>> +  return false;
>> +}
> So from a compile-time standpoint, would it be better to to assign UIDs 
> to each statement so that within a block you can just compare the UIDs?  
> That's a pretty standard way to deal with the problem of statement 
> domination within a block if we're going to be doing multiple queries.

I'd considered it but because statement UIDs don't exist at
the start of a pass, assigning them means either traversing all
statements in the whole CFG first, even in functions with no
deallocation calls or clobbers, or doing it lazily, after
the first such statement has been seen.  It might ultimately
be worthwhile if more warnings(*) end up relying on it but at
this point I'm not sure the optimization wouldn't end up slowing
things down on average.

For some data, in a GCC bootstrap, each statement visited by
this loop is visited on average twice (2.2 times), and
the average sequence of statements traversed by the loop is
2.65, with a maximum of 22 times and 18 statements, respectively.
So still not sure it would be a win.

Let me know if this is something you think I need to pursue at
this stage.

[*] I think simple memory/resource leak detection might perhaps
be one.

>> +
>> +/* Return true if P and Q point to the same object, and false if they
>> +   either don't or their relationship cannot be determined.  */
>> +
>> +static bool
>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>> +{
>> +  if (!ptr_derefs_may_alias_p (p, q))
>> +    return false;
> Hmm, I guess that you don't need to worry about the case where P and Q 
> point to different elements within an array.  They point to different 
> final objects, though they do share a common enclosing object.  
> Similarly for P & Q pointing to different members within a structure.

Right.  The if statement is an optimization to avoid having to
determine the identity of the complete objects that P and Q
point to.  That's done by the calls to get_ref() below (for
complete objects; as you note, we don't care about subobjects
for this).

>> +
>> +/* For a STMT either a call to a deallocation function or a clobber, warn
>> +   for uses of the pointer PTR it was called with (including its copies
>> +   or others derived from it by pointer arithmetic).  */
>> +
>> +void
>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>> +{
>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>> +
>> +  const bool check_dangling = !is_gimple_call (stmt);
>> +  basic_block stmt_bb = gimple_bb (stmt);
>> +
>> +  /* If the deallocation (or clobber) statement dominates more than
>> +     a single basic block issue a "maybe" k
> That seems wrong.   What you're looking for is a post-dominance 
> relationship I think.   If the sink (free/delete) is post-dominated by 
> the use, then it's a "must", if it's not post-dominated, then it's a 
> maybe.  Of course, that means you need to build post-dominators.

I'm sure you're right in general.  To avoid false positives
the warning is very simplistic and only considers straight
paths through the CFG, so I'm not sure this matters.  But
I'm fine with using the post-dominance test instead if you
thin it's worthwhile (it doesn't change any tests).

>> +
>> +	  if (check_dangling
>> +	      && gimple_code (use_stmt) == GIMPLE_RETURN
>> +	      && optimize && flag_isolate_erroneous_paths_dereference)
>> +	    /* Avoid interfering with -Wreturn-local-addr (which runs only
>> +	       with optimization enabled).  */
>> +	    continue;
> Umm, that looks like a hack.  I can't think of a good reason why removal 
> of erroneous paths should gate any of this code.  ISTM that you're 
> likely papering over a problem elsewhere.

This code avoids issuing -Wdangling-pointer for problems that
will later be diagnosed by -Wreturn-local-addr.  E.g., in this
case from Wreturn-local-addr-2.c:

   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" } */
   }

Without the test we'd end up with

   warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]

in addition to -Wreturn-local-addr (and a whole slew of
failures in the -Wreturn-local-addr tests).

-Wreturn-local-addr only runs when
flag_isolate_erroneous_paths_dereference is nonzero, so
the conditional makes sure -Wdangling-pointer is issued when
either the flag or -Wreturn-local-addr is disabled.  I think
that works as expected (i.e., there's no problem elsewhere).

I could have the code issue -Wdangling-pointer and suppress
-Wreturn-local-addr but that doesn't seem right since
the pointer hasn't gone out of scope yet at the point it's
returned.

Alternatively, I could change this instance of
-Wdangling-pointer to -Wreturn-local-addr but that also
doesn't seem like good design since we have a whole pass
dedicated to the latter warning.

I can't think of any other more elegant solutions but I'm open
to suggestions.

Martin

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

* [PATCH v2 1/2] add -Wuse-after-free
  2021-11-23 21:16     ` Martin Sebor
@ 2021-11-30 22:32       ` Martin Sebor
  2021-12-07  0:50         ` PING " Martin Sebor
  2022-01-11 22:40         ` Jason Merrill
  0 siblings, 2 replies; 34+ messages in thread
From: Martin Sebor @ 2021-11-30 22:32 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

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

Attached is a revised patch with the following changes based
on your comments:

1) Set and use statement uids to determine which statement
    precedes which in the same basic block.
2) Avoid testing flag_isolate_erroneous_paths_dereference.
3) Use post-dominance to decide whether to use the "maybe"
    phrasing vs a definite form.

David raised (and in our offline discussion today reiterated)
an objection to the default setting of the option being
the strictest.  I have not changed that in this revision.
See my rationale for this choice in my reply below:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html

Martin

On 11/23/21 2:16 PM, Martin Sebor wrote:
> On 11/22/21 6:32 PM, Jeff Law wrote:
>>
>>
>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>> Patch 1 in the series detects a small subset of uses of pointers
>>> made indeterminate by calls to deallocation functions like free
>>> or C++ operator delete.  To control the conditions the warnings
>>> are issued under the new -Wuse-after-free= option provides three
>>> levels.  At the lowest level the warning triggers only for
>>> unconditional uses of freed pointers and doesn't warn for uses
>>> in equality expressions.  Level 2 warns also for come conditional
>>> uses, and level 3 also for uses in equality expressions.
>>>
>>> I debated whether to make level 2 or 3 the default included in
>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>> of both the problem and GCC's new ability to detect it: using
>>> a pointer after it's been freed, even only in principle, by
>>> a successful call to realloc, is undefined, and 2) because
>>> it's trivial to lower the level either globally, or locally
>>> by suppressing the warning around such misuses.
>>>
>>> I've tested the patch on x86_64-linux and by building Glibc
>>> and Binutils/GDB.  It triggers a number of times in each, all
>>> due to comparing invalidated pointers for equality (i.e., level
>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>> and will see how the Glibc folks want to deal with theirs (I
>>> track them in BZ #28521).
>>>
>>> The tests contain a number of xfails due to limitations I'm
>>> aware of.  I marked them pr?????? until the patch is approved.
>>> I will open bugs for them before committing if I don't resolve
>>> them in a followup.
>>>
>>> Martin
>>>
>>> gcc-63272-1.diff
>>>
>>> Add -Wuse-after-free.
>>>
>>> gcc/c-family/ChangeLog
>>>
>>>     * c.opt (-Wuse-after-free): New options.
>>>
>>> gcc/ChangeLog:
>>>
>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>     (pass_waccess::check_call_access): ...to this.
>>>     (pass_waccess::check): Rename...
>>>     (pass_waccess::check_block): ...to this.
>>>     (pass_waccess::check_pointer_uses): New function.
>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>     (gimple_use_after_inval_p): New function.
>>>     (get_realloc_lhs): New function.
>>>     (maybe_warn_mismatched_realloc): New function.
>>>     (pointers_related_p): New function.
>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>     (pass_waccess::execute): Compute and free dominance info.
>>>
>>> libcpp/ChangeLog:
>>>
>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>     an invalid one to avoid -Wuse-0after-free.
>>>
>>> libiberty/ChangeLog:
>>>
>>>     * regex.c: Suppress -Wuse-after-free.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>
>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>> b/gcc/gimple-ssa-warn-access.cc
>>> index 63fc27a1487..2065402a2b9 100644
>>> --- a/gcc/gimple-ssa-warn-access.cc
>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>
>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>> (gcall *call)
>>>       }
>>>   }
>>> +/* Return true if either USE_STMT's basic block (that of a pointer's 
>>> use)
>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>> statement,
>>> +   which is either a clobber or a deallocation call), or if they're in
>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>> +
>>> +static bool
>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>> +              bool last_block = false)
>>> +{
>>> +  tree clobvar =
>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : 
>>> NULL_TREE;
>>> +
>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>> +
>>> +  if (inval_bb != use_bb)
>>> +    {
>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>> +    return true;
>>> +
>>> +      if (!clobvar || !last_block)
>>> +    return false;
>>> +
>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>> +
>>> +      auto_bitmap visited;
>>> +
>>> +      /* A use statement in the last basic block in a function or 
>>> one that
>>> +     falls through to it is after any other prior clobber of the used
>>> +     variable unless it's followed by a clobber of the same 
>>> variable. */
>>> +      basic_block bb = use_bb;
>>> +      while (bb != inval_bb
>>> +         && single_succ_p (bb)
>>> +         && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
>>> +    {
>>> +      if (!bitmap_set_bit (visited, bb->index))
>>> +        /* Avoid cycles. */
>>> +        return true;
>>> +
>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>> +        {
>>> +          gimple *stmt = gsi_stmt (gsi);
>>> +          if (gimple_clobber_p (stmt))
>>> +        {
>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>> +            /* The use is followed by a clobber.  */
>>> +            return false;
>>> +        }
>>> +        }
>>> +
>>> +      bb = single_succ (bb);
>>> +      gsi = gsi_start_bb (bb);
>>> +    }
>>> +
>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>> +    }
>> ?!?  I would have thought the block dominance test plus checking UIDs 
>> if the two statements are in the same block would be all you need.  
>> Can you elaborate more on what that hunk above is trying to do?
> 
> The loop is entered only for -Wdangling-pointer.  It looks for
> the first clobber of the CLOBVAR variable (one whose clobber
> statement has been seen during the CFG traversal and whose use
> is being validated) in the successors along the single edge
> from the use block.  If the search finds a clobber, the use
> is valid.  If it doesn't, the use is one of a variable having
> gone out of scope (the clobber must be before the use).
> 
> Among the cases the loop handles is the one in PR 63272
> (the request for -Wdangling-pointer) where the use neither
> follows the clobber in the same block nor dominated by it.
> 
> There may be a way to optimize it somehow but because it's
> a search I don't think a simple UID check alone would be
> enough.
> 
>>> +
>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>> +       gsi_next_nondebug (&si))
>>> +    {
>>> +      gimple *stmt = gsi_stmt (si);
>>> +      if (stmt == use_stmt)
>>> +    return true;
>>> +    }
>>> +
>>> +  return false;
>>> +}
>> So from a compile-time standpoint, would it be better to to assign 
>> UIDs to each statement so that within a block you can just compare the 
>> UIDs? That's a pretty standard way to deal with the problem of 
>> statement domination within a block if we're going to be doing 
>> multiple queries.
> 
> I'd considered it but because statement UIDs don't exist at
> the start of a pass, assigning them means either traversing all
> statements in the whole CFG first, even in functions with no
> deallocation calls or clobbers, or doing it lazily, after
> the first such statement has been seen.  It might ultimately
> be worthwhile if more warnings(*) end up relying on it but at
> this point I'm not sure the optimization wouldn't end up slowing
> things down on average.
> 
> For some data, in a GCC bootstrap, each statement visited by
> this loop is visited on average twice (2.2 times), and
> the average sequence of statements traversed by the loop is
> 2.65, with a maximum of 22 times and 18 statements, respectively.
> So still not sure it would be a win.
> 
> Let me know if this is something you think I need to pursue at
> this stage.
> 
> [*] I think simple memory/resource leak detection might perhaps
> be one.
> 
>>> +
>>> +/* Return true if P and Q point to the same object, and false if they
>>> +   either don't or their relationship cannot be determined.  */
>>> +
>>> +static bool
>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>>> +{
>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>> +    return false;
>> Hmm, I guess that you don't need to worry about the case where P and Q 
>> point to different elements within an array.  They point to different 
>> final objects, though they do share a common enclosing object. 
>> Similarly for P & Q pointing to different members within a structure.
> 
> Right.  The if statement is an optimization to avoid having to
> determine the identity of the complete objects that P and Q
> point to.  That's done by the calls to get_ref() below (for
> complete objects; as you note, we don't care about subobjects
> for this).
> 
>>> +
>>> +/* For a STMT either a call to a deallocation function or a clobber, 
>>> warn
>>> +   for uses of the pointer PTR it was called with (including its copies
>>> +   or others derived from it by pointer arithmetic).  */
>>> +
>>> +void
>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>> +{
>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>> +
>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>> +
>>> +  /* If the deallocation (or clobber) statement dominates more than
>>> +     a single basic block issue a "maybe" k
>> That seems wrong.   What you're looking for is a post-dominance 
>> relationship I think.   If the sink (free/delete) is post-dominated by 
>> the use, then it's a "must", if it's not post-dominated, then it's a 
>> maybe.  Of course, that means you need to build post-dominators.
> 
> I'm sure you're right in general.  To avoid false positives
> the warning is very simplistic and only considers straight
> paths through the CFG, so I'm not sure this matters.  But
> I'm fine with using the post-dominance test instead if you
> thin it's worthwhile (it doesn't change any tests).
> 
>>> +
>>> +      if (check_dangling
>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs only
>>> +           with optimization enabled).  */
>>> +        continue;
>> Umm, that looks like a hack.  I can't think of a good reason why 
>> removal of erroneous paths should gate any of this code.  ISTM that 
>> you're likely papering over a problem elsewhere.
> 
> This code avoids issuing -Wdangling-pointer for problems that
> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
> case from Wreturn-local-addr-2.c:
> 
>    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" } */
>    }
> 
> Without the test we'd end up with
> 
>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
> 
> in addition to -Wreturn-local-addr (and a whole slew of
> failures in the -Wreturn-local-addr tests).
> 
> -Wreturn-local-addr only runs when
> flag_isolate_erroneous_paths_dereference is nonzero, so
> the conditional makes sure -Wdangling-pointer is issued when
> either the flag or -Wreturn-local-addr is disabled.  I think
> that works as expected (i.e., there's no problem elsewhere).
> 
> I could have the code issue -Wdangling-pointer and suppress
> -Wreturn-local-addr but that doesn't seem right since
> the pointer hasn't gone out of scope yet at the point it's
> returned.
> 
> Alternatively, I could change this instance of
> -Wdangling-pointer to -Wreturn-local-addr but that also
> doesn't seem like good design since we have a whole pass
> dedicated to the latter warning.
> 
> I can't think of any other more elegant solutions but I'm open
> to suggestions.
> 
> Martin


[-- Attachment #2: gcc-63272-1.diff --]
[-- Type: text/x-patch, Size: 55859 bytes --]

Add -Wuse-after-free.

gcc/c-family/ChangeLog

	* c.opt (-Wuse-after-free): New options.

gcc/ChangeLog:

	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
	OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
	* diagnostic-spec.h (NW_DANGLING): New enumerator.
	* doc/invoke.texi (-Wuse-after-free): Document new option.
	* gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
	(pass_waccess::check_call_access): ...to this.
	(pass_waccess::check): Rename...
	(pass_waccess::check_block): ...to this.
	(pass_waccess::check_pointer_uses): New function.
	(pass_waccess::gimple_call_return_arg): New function.
	(pass_waccess::warn_invalid_pointer): New function.
	(pass_waccess::check_builtin): Handle free and realloc.
	(gimple_use_after_inval_p): New function.
	(get_realloc_lhs): New function.
	(maybe_warn_mismatched_realloc): New function.
	(pointers_related_p): New function.
	(pass_waccess::check_call): Call check_pointer_uses.
	(pass_waccess::execute): Compute and free dominance info.

libcpp/ChangeLog:

	* files.c (_cpp_find_file): Substitute a valid pointer for
	an invalid one to avoid -Wuse-after-free.

libiberty/ChangeLog:

	* regex.c: Suppress -Wuse-after-free.

gcc/testsuite/ChangeLog:

	* gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
	* gcc.dg/Wmismatched-dealloc-3.c: Same.
	* gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
	* gcc.dg/attr-alloc_size-7.c: Same.
	* c-c++-common/Wuse-after-free-2.c: New test.
	* c-c++-common/Wuse-after-free-3.c: New test.
	* c-c++-common/Wuse-after-free-4.c: New test.
	* c-c++-common/Wuse-after-free-5.c: New test.
	* c-c++-common/Wuse-after-free-6.c: New test.
	* c-c++-common/Wuse-after-free-7.c: New test.
	* c-c++-common/Wuse-after-free.c: New test.
	* g++.dg/warn/Wmismatched-dealloc-3.C: New test.
	* g++.dg/warn/Wuse-after-free.C: New test.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 4b8a094b206..fb1abc0de4c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1362,6 +1362,14 @@ Wunused-const-variable=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_unused_const_variable) Warning LangEnabledBy(C ObjC,Wunused-variable, 1, 0) IntegerRange(0, 2)
 Warn when a const variable is unused.
 
+Wuse-after-free
+C ObjC C++ LTO ObjC++ Alias(Wuse-after-free=, 2, 0) Warning
+Warn for uses of pointers to deallocated strorage.
+
+Wuse-after-free=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_use_after_free) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 3, 0) IntegerRange(0, 3)
+Warn for uses of pointers to deallocated strorage.
+
 Wvariadic-macros
 C ObjC C++ ObjC++ CPP(warn_variadic_macros) CppReason(CPP_W_VARIADIC_MACROS) Var(cpp_warn_variadic_macros) Init(0) Warning LangEnabledBy(C ObjC C++ ObjC++,Wpedantic || Wtraditional)
 Warn about using variadic macros.
diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c
index d1e563d19ba..921e7ab7423 100644
--- a/gcc/diagnostic-spec.c
+++ b/gcc/diagnostic-spec.c
@@ -99,6 +99,11 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt)
 	m_bits = NW_UNINIT;
       break;
 
+    case OPT_Wreturn_local_addr:
+    case OPT_Wuse_after_free_:
+      m_bits = NW_DANGLING;
+      break;
+
     default:
       /* A catchall group for everything else.  */
       m_bits = NW_OTHER;
diff --git a/gcc/diagnostic-spec.h b/gcc/diagnostic-spec.h
index 368b75f3254..897afa1f3bd 100644
--- a/gcc/diagnostic-spec.h
+++ b/gcc/diagnostic-spec.h
@@ -41,11 +41,13 @@ public:
      NW_UNINIT = 1 << 3,
      /* Warnings about arithmetic overflow.  */
      NW_VFLOW = 1 << 4,
+     /* Warnings about dangling pointers.  */
+     NW_DANGLING = 1 << 5,
      /* All other unclassified warnings.  */
-     NW_OTHER = 1 << 5,
+     NW_OTHER = 1 << 6,
      /* All groups of warnings.  */
      NW_ALL = (NW_ACCESS | NW_LEXICAL | NW_NONNULL
-	       | NW_UNINIT | NW_VFLOW | NW_OTHER)
+	       | NW_UNINIT | NW_VFLOW | NW_DANGLING | NW_OTHER)
    };
 
   nowarn_spec_t (): m_bits () { }
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 3bddfbaae6a..46bc8046436 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -4365,6 +4365,60 @@ annotations.
 Warn about overriding virtual functions that are not marked with the
 @code{override} keyword.
 
+@item -Wuse-after-free
+@itemx -Wuse-after-free=@var{n}
+@opindex Wuse-after-free
+@opindex Wno-use-after-free
+Warn about uses of pointers to dynamically allocated objects that have
+been rendered indeterminate by a call to a deallocation function.
+
+@table @gcctabopt
+@item -Wuse-after-free=1
+At level 1 the warning attempts to diagnose only unconditional uses of
+pointers made indeterminate by a deallocation call.  This includes
+double-@code{free} calls.  Although undefined, uses of indeterminate
+pointers in equality (or inequality) expressions are not diagnosed at
+this level.
+@item -Wuse-after-free=2
+At level 2, in addition to unconditional uses the warning also diagnoses
+conditional uses of pointers made indeterminate by a deallocation call.
+As at level 1, uses in (or inequality) equality expressions are not
+diagnosed.  For example, the second call to @code{free} in the following
+function is diagnosed at this level:
+@smallexample
+struct A @{ int refcount; void *data; @};
+
+void release (struct A *p)
+@{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data);   // warning: p may be used after free
+@}
+@end smallexample
+@item -Wuse-after-free=3
+At level 3, the warning also diagnoses uses of indeterminate pointers in
+equality expressions.  All uses of indeterminate pointers are undefined
+but equality tests sometimes appear after calls to @code{realloc} as
+an attempt to determine whether the call resulted in relocating the object
+to a different address.  They are diagnosed at a separate level to aid
+legacy code gradually transition to safe alternatives.  For example,
+the equality test in the function below is diagnosed at this level:
+@smallexample
+void adjust_pointers (int**, int);
+
+void grow (int **p, int n)
+@{
+  int **q = (int**)realloc (p, n *= 2);
+  if (q == p)
+    return;
+  adjust_pointers ((int**)q, n);
+@}
+@end smallexample
+@end table
+
+@option{-Wuse-after-free=3} is included in @option{-Wall}.
+
 @item -Wuseless-cast @r{(C++ and Objective-C++ only)}
 @opindex Wuseless-cast
 @opindex Wno-useless-cast
@@ -5685,6 +5739,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wuse-after-free=3  @gol
 -Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index 48bf8aaff50..e396266088f 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -50,6 +50,7 @@
 #include "stringpool.h"
 #include "attribs.h"
 #include "demangle.h"
+#include "attr-fnspec.h"
 #include "pointer-query.h"
 
 /* Return true if tree node X has an associated location.  */
@@ -2068,6 +2069,7 @@ class pass_waccess : public gimple_opt_pass
   opt_pass *clone () { return new pass_waccess (m_ctxt); }
 
   virtual bool gate (function *);
+
   virtual unsigned int execute (function *);
 
 private:
@@ -2081,14 +2083,14 @@ private:
   /* Check a call to a built-in function.  */
   bool check_builtin (gcall *);
 
-  /* Check a call to an ordinary function.  */
-  bool check_call (gcall *);
+  /* Check a call to an ordinary function for invalid accesses.  */
+  bool check_call_access (gcall *);
 
   /* Check statements in a basic block.  */
-  void check (basic_block);
+  void check_block (basic_block);
 
   /* Check a call to a function.  */
-  void check (gcall *);
+  void check_call (gcall *);
 
   /* Check a call to the named built-in function.  */
   void check_alloca (gcall *);
@@ -2104,10 +2106,27 @@ private:
   void maybe_check_dealloc_call (gcall *);
   void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *);
 
+  /* Check for uses of indeterminate pointers.  */
+  void check_pointer_uses (gimple *, tree);
+
+  /* Return the argument that a call returns.  */
+  tree gimple_call_return_arg (gcall *);
+
+  void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
+
+  /* Return true if use follows an invalidating statement.  */
+  bool use_after_inval_p (gimple *, gimple *);
+
   /* A pointer_query object and its cache to store information about
      pointers and their targets in.  */
   pointer_query m_ptr_qry;
   pointer_query::cache_type m_var_cache;
+
+  /* A bit is set for each basic block whose statements have been assigned
+     valid UIDs.  */
+  bitmap m_bb_uids_set;
+  /* The current function.  */
+  function *m_func;
 };
 
 /* Construct the pass.  */
@@ -2115,7 +2134,9 @@ private:
 pass_waccess::pass_waccess (gcc::context *ctxt)
   : gimple_opt_pass (pass_data_waccess, ctxt),
     m_ptr_qry (NULL, &m_var_cache),
-    m_var_cache ()
+    m_var_cache (),
+    m_bb_uids_set (),
+    m_func ()
 {
 }
 
@@ -2795,6 +2816,15 @@ pass_waccess::check_builtin (gcall *stmt)
       check_read_access (stmt, call_arg (stmt, 0));
       return true;
 
+    case BUILT_IN_FREE:
+    case BUILT_IN_REALLOC:
+      {
+	tree arg = call_arg (stmt, 0);
+	if (TREE_CODE (arg) == SSA_NAME)
+	  check_pointer_uses (stmt, arg);
+      }
+      return true;
+
     case BUILT_IN_GETTEXT:
     case BUILT_IN_PUTS:
     case BUILT_IN_PUTS_UNLOCKED:
@@ -2899,6 +2929,7 @@ pass_waccess::check_builtin (gcall *stmt)
 	return true;
       break;
     }
+
   return false;
 }
 
@@ -3224,7 +3255,7 @@ pass_waccess::maybe_check_access_sizes (rdwr_map *rwm, tree fndecl, tree fntype,
    accesses.  Return true if a call has been handled.  */
 
 bool
-pass_waccess::check_call (gcall *stmt)
+pass_waccess::check_call_access (gcall *stmt)
 {
   tree fntype = gimple_call_fntype (stmt);
   if (!fntype)
@@ -3412,46 +3443,442 @@ pass_waccess::maybe_check_dealloc_call (gcall *call)
     }
 }
 
+/* Return true if either USE_STMT's basic block (that of a pointer's use)
+   is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
+   or if they're in the same block, USE_STMT follows INVAL_STMT.  */
+
+bool
+pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt)
+{
+  basic_block inval_bb = gimple_bb (inval_stmt);
+  basic_block use_bb = gimple_bb (use_stmt);
+
+  if (inval_bb != use_bb)
+    return dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb);
+
+  if (bitmap_set_bit (m_bb_uids_set, inval_bb->index))
+    /* The first time this basic block is visited assign increasing ids
+       to consecutive statements in it.  Use the ids to determine which
+       precedes which.  This avoids the linear traversal on subsequent
+       visits to the same block.  */
+    for (auto si = gsi_start_bb (inval_bb); !gsi_end_p (si);
+	 gsi_next_nondebug (&si))
+      {
+	gimple *stmt = gsi_stmt (si);
+	unsigned uid = inc_gimple_stmt_max_uid (m_func);
+	gimple_set_uid (stmt, uid);
+      }
+
+  return gimple_uid (inval_stmt) < gimple_uid (use_stmt);
+}
+
+/* Issue a warning for the USE_STMT of pointer PTR rendered invalid
+   by INVAL_STMT.  PTR may be null when it's been optimized away.
+   MAYBE is true to issue the "maybe" kind of warning.  EQUALITY is
+   true when the pointer is used in an equality expression.  */
+
+void
+pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
+				    gimple *inval_stmt,
+				    bool maybe,
+				    bool equality /* = false */)
+{
+  /* Avoid printing the unhelpful "<unknown>" in the diagnostics.  */
+  if (ptr && TREE_CODE (ptr) == SSA_NAME
+      && (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr))))
+    ptr = NULL_TREE;
+
+  location_t use_loc = gimple_location (use_stmt);
+  if (use_loc == UNKNOWN_LOCATION)
+    {
+      use_loc = cfun->function_end_locus;
+      if (!ptr)
+	/* Avoid issuing a warning with no context other than
+	   the function.  That would make it difficult to debug
+	   in any but very simple cases.  */
+	return;
+    }
+
+  if (is_gimple_call (inval_stmt))
+    {
+      if ((equality && warn_use_after_free < 3)
+	  || (maybe && warn_use_after_free < 2)
+	  || warning_suppressed_p (use_stmt, OPT_Wuse_after_free_))
+	return;
+
+      const tree inval_decl = gimple_call_fndecl (inval_stmt);
+
+      if ((ptr && warning_at (use_loc, OPT_Wuse_after_free_,
+			      (maybe
+			       ? G_("pointer %qE may be used after %qD")
+			       : G_("pointer %qE used after %qD")),
+			      ptr, inval_decl))
+	  || (!ptr && warning_at (use_loc, OPT_Wuse_after_free_,
+			      (maybe
+			       ? G_("pointer may be used after %qD")
+			       : G_("pointer used after %qD")),
+				  inval_decl)))
+	{
+	  location_t loc = gimple_location (inval_stmt);
+	  inform (loc, "call to %qD here", inval_decl);
+	  suppress_warning (use_stmt, OPT_Wuse_after_free_);
+	}
+      return;
+    }
+}
+
+/* If STMT is a call to either the standard realloc or to a user-defined
+   reallocation function returns its LHS and set *PTR to the reallocated
+   pointer.  Otherwise return null.  */
+
+static tree
+get_realloc_lhs (gimple *stmt, tree *ptr)
+{
+  if (gimple_call_builtin_p (stmt, BUILT_IN_REALLOC))
+    {
+      *ptr = gimple_call_arg (stmt, 0);
+      return gimple_call_lhs (stmt);
+    }
+
+  gcall *call = dyn_cast<gcall *>(stmt);
+  if (!call)
+    return NULL_TREE;
+
+  tree fnattr = NULL_TREE;
+  tree fndecl = gimple_call_fndecl (call);
+  if (fndecl)
+    fnattr = DECL_ATTRIBUTES (fndecl);
+  else
+    {
+      tree fntype = gimple_call_fntype (stmt);
+      if (!fntype)
+	return NULL_TREE;
+      fnattr = TYPE_ATTRIBUTES (fntype);
+    }
+
+  if (!fnattr)
+    return NULL_TREE;
+
+  for (tree ats = fnattr;  (ats = lookup_attribute ("*dealloc", ats));
+       ats = TREE_CHAIN (ats))
+    {
+      tree args = TREE_VALUE (ats);
+      if (!args)
+	continue;
+
+      tree alloc = TREE_VALUE (args);
+      if (!alloc)
+	continue;
+
+      if (alloc == DECL_NAME (fndecl))
+	{
+	  unsigned argno = 0;
+	  if (tree index = TREE_CHAIN (args))
+	    argno = TREE_INT_CST_LOW (TREE_VALUE (index)) - 1;
+	  *ptr = gimple_call_arg (stmt, argno);
+	  return gimple_call_lhs (stmt);
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* Warn if STMT is a call to a deallocation function that's not a match
+   for the REALLOC_STMT call.  Return true if warned.  */
+
+static bool
+maybe_warn_mismatched_realloc (tree ptr, gimple *realloc_stmt, gimple *stmt)
+{
+  if (!is_gimple_call (stmt))
+    return false;
+
+  tree fndecl = gimple_call_fndecl (stmt);
+  if (!fndecl)
+    return false;
+
+  unsigned argno = fndecl_dealloc_argno (fndecl);
+  if (call_nargs (stmt) <= argno)
+    return false;
+
+  if (matching_alloc_calls_p (realloc_stmt, fndecl))
+    return false;
+
+  /* Avoid printing the unhelpful "<unknown>" in the diagnostics.  */
+  if (ptr && TREE_CODE (ptr) == SSA_NAME
+      && (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr))))
+    ptr = NULL_TREE;
+
+  location_t loc = gimple_location (stmt);
+  tree realloc_decl = gimple_call_fndecl (realloc_stmt);
+  tree dealloc_decl = gimple_call_fndecl (stmt);
+  if (ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
+			  "%qD called on pointer %qE passed to mismatched "
+			  "allocation function %qD",
+			  dealloc_decl, ptr, realloc_decl))
+    return false;
+  if (!ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
+			   "%qD called on a pointer passed to mismatched "
+			   "reallocation function %qD",
+			   dealloc_decl, realloc_decl))
+    return false;
+
+  inform (gimple_location (realloc_stmt),
+	  "call to %qD", realloc_decl);
+  return true;
+}
+
+/* Return true if P and Q point to the same object, and false if they
+   either don't or their relationship cannot be determined.  */
+
+static bool
+pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
+{
+  if (!ptr_derefs_may_alias_p (p, q))
+    return false;
+
+  /* TODO: Work harder to rule out relatedness.  */
+  access_ref pref, qref;
+  if (!qry.get_ref (p, stmt, &pref, 0)
+      || !qry.get_ref (q, stmt, &qref, 0))
+    return true;
+
+  return pref.ref == qref.ref;
+}
+
+/* For a STMT either a call to a deallocation function or a clobber, warn
+   for uses of the pointer PTR it was called with (including its copies
+   or others derived from it by pointer arithmetic).  */
+
+void
+pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
+{
+  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
+
+  const bool check_dangling = !is_gimple_call (stmt);
+  basic_block stmt_bb = gimple_bb (stmt);
+
+  /* If STMT is a reallocation function set to the reallocated pointer
+     and the LHS of the call, respectively.  */
+  tree realloc_ptr = NULL_TREE;
+  tree realloc_lhs = get_realloc_lhs (stmt, &realloc_ptr);
+
+  auto_bitmap visited;
+
+  auto_vec<tree> pointers;
+  pointers.safe_push (ptr);
+
+  /* Starting with PTR, iterate over POINTERS added by the loop, and
+     either warn for their uses in basic blocks dominated by the STMT
+     or in statements that follow it in the same basic block, or add
+     them to POINTERS if they point into the same object as PTR (i.e.,
+     are obtained by pointer arithmetic on PTR).  */
+  for (unsigned i = 0; i != pointers.length (); ++i)
+    {
+      tree ptr = pointers[i];
+      if (TREE_CODE (ptr) == SSA_NAME
+	  && !bitmap_set_bit (visited, SSA_NAME_VERSION (ptr)))
+	/* Avoid revisiting the same pointer.  */
+	continue;
+
+      use_operand_p use_p;
+      imm_use_iterator iter;
+      FOR_EACH_IMM_USE_FAST (use_p, iter, ptr)
+	{
+	  gimple *use_stmt = USE_STMT (use_p);
+	  if (use_stmt == stmt || is_gimple_debug (use_stmt))
+	    continue;
+
+	  if (realloc_lhs)
+	    {
+	      /* Check to see if USE_STMT is a mismatched deallocation
+		 call for the pointer passed to realloc.  That's a bug
+		 regardless of the pointer's value and so warn.  */
+	      if (maybe_warn_mismatched_realloc (*use_p->use, stmt, use_stmt))
+		continue;
+
+	      /* Pointers passed to realloc that are used in basic blocks
+		 where the realloc call is known to have failed are valid.
+		 Ignore pointers that nothing is known about.  Those could
+		 have escaped along with their nullness.  */
+	      value_range vr;
+	      if (m_ptr_qry.rvals->range_of_expr (vr, realloc_lhs, use_stmt))
+		{
+		  if (vr.zero_p ())
+		    continue;
+
+		  if (!pointers_related_p (stmt, ptr, realloc_ptr, m_ptr_qry))
+		    continue;
+		}
+	    }
+
+	  if (check_dangling
+	      && gimple_code (use_stmt) == GIMPLE_RETURN)
+	    /* Avoid interfering with -Wreturn-local-addr (which runs only
+	       with optimization enabled so it won't diagnose cases that
+	       would be caught here when optimization is disabled).  */
+	    continue;
+
+	  bool equality = false;
+	  if (is_gimple_assign (use_stmt))
+	    {
+	      tree_code code = gimple_assign_rhs_code (use_stmt);
+	      equality = code == EQ_EXPR || code == NE_EXPR;
+	    }
+	  else if (gcond *cond = dyn_cast<gcond *>(use_stmt))
+	    {
+	      tree_code code = gimple_cond_code (cond);
+	      equality = code == EQ_EXPR || code == NE_EXPR;
+	    }
+
+	  /* Warn if USE_STMT is dominated by the deallocation STMT.
+	     Otherwise, add the pointer to POINTERS so that the uses
+	     of any other pointers derived from it can be checked.  */
+	  if (use_after_inval_p (stmt, use_stmt))
+	    {
+	      /* TODO: Handle PHIs but careful of false positives.  */
+	      if (gimple_code (use_stmt) != GIMPLE_PHI)
+		{
+		  basic_block use_bb = gimple_bb (use_stmt);
+		  bool this_maybe
+		    = !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb);
+		  warn_invalid_pointer (*use_p->use, use_stmt, stmt,
+					this_maybe, equality);
+		  continue;
+		}
+	    }
+
+	  if (is_gimple_assign (use_stmt))
+	    {
+	      tree lhs = gimple_assign_lhs (use_stmt);
+	      if (TREE_CODE (lhs) == SSA_NAME)
+		{
+		  tree_code rhs_code = gimple_assign_rhs_code (use_stmt);
+		  if (rhs_code == POINTER_PLUS_EXPR || rhs_code == SSA_NAME)
+		    pointers.safe_push (lhs);
+		}
+	      continue;
+	    }
+
+	  if (gcall *call = dyn_cast <gcall *>(use_stmt))
+	    {
+	      if (gimple_call_return_arg (call))
+		if (tree lhs = gimple_call_lhs (call))
+		  if (TREE_CODE (lhs) == SSA_NAME)
+		    pointers.safe_push (lhs);
+	      continue;
+	    }
+	}
+    }
+}
+
 /* Check call STMT for invalid accesses.  */
 
 void
-pass_waccess::check (gcall *stmt)
+pass_waccess::check_call (gcall *stmt)
 {
   if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
     check_builtin (stmt);
 
-  if (is_gimple_call (stmt))
-    check_call (stmt);
+  if (tree callee = gimple_call_fndecl (stmt))
+    {
+      /* Check for uses of the pointer passed to either a standard
+	 or a user-defined deallocation function.  */
+      unsigned argno = fndecl_dealloc_argno (callee);
+      if (argno < (unsigned) call_nargs (stmt))
+	{
+	  tree arg = call_arg (stmt, argno);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    check_pointer_uses (stmt, arg);
+	}
+    }
 
-  maybe_check_dealloc_call (stmt);
+  check_call_access (stmt);
 
+  maybe_check_dealloc_call (stmt);
   check_nonstring_args (stmt);
 }
 
+
 /* Check basic block BB for invalid accesses.  */
 
 void
-pass_waccess::check (basic_block bb)
+pass_waccess::check_block (basic_block bb)
 {
   /* Iterate over statements, looking for function calls.  */
-  for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
+  for (auto si = gsi_start_bb (bb); !gsi_end_p (si);
+       gsi_next_nondebug (&si))
     {
-      if (gcall *call = dyn_cast <gcall *> (gsi_stmt (si)))
-	check (call);
+      gimple *stmt = gsi_stmt (si);
+      if (gcall *call = dyn_cast <gcall *> (stmt))
+	check_call (call);
     }
 }
 
+/* Return the argument that the call STMT to a built-in function returns
+   (including with an offset) or null if it doesn't.  */
+
+tree
+pass_waccess::gimple_call_return_arg (gcall *call)
+{
+  /* Check for attribute fn spec to see if the function returns one
+     of its arguments.  */
+  attr_fnspec fnspec = gimple_call_fnspec (call);
+  unsigned int argno;
+  if (!fnspec.returns_arg (&argno))
+    {
+      if (gimple_call_num_args (call) < 1)
+	return NULL_TREE;
+
+      if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL))
+	return NULL_TREE;
+
+      tree fndecl = gimple_call_fndecl (call);
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_MEMPCPY:
+	case BUILT_IN_MEMPCPY_CHK:
+	case BUILT_IN_MEMCHR:
+	case BUILT_IN_STRCHR:
+	case BUILT_IN_STRRCHR:
+	case BUILT_IN_STRSTR:
+	case BUILT_IN_STPCPY:
+	case BUILT_IN_STPCPY_CHK:
+	case BUILT_IN_STPNCPY:
+	case BUILT_IN_STPNCPY_CHK:
+	  argno = 0;
+	  break;
+
+	default:
+	  return NULL_TREE;
+	}
+    }
+
+  if (gimple_call_num_args (call) <= argno)
+    return NULL_TREE;
+
+  return gimple_call_arg (call, argno);
+}
+
 /* Check function FUN for invalid accesses.  */
 
 unsigned
 pass_waccess::execute (function *fun)
 {
+  calculate_dominance_info (CDI_DOMINATORS);
+  calculate_dominance_info (CDI_POST_DOMINATORS);
+
   /* Create a new ranger instance and associate it with FUN.  */
   m_ptr_qry.rvals = enable_ranger (fun);
+  m_func = fun;
+
+  auto_bitmap bb_uids_set (&bitmap_default_obstack);
+  m_bb_uids_set = bb_uids_set;
+
+  set_gimple_stmt_max_uid (m_func, 0);
 
   basic_block bb;
   FOR_EACH_BB_FN (bb, fun)
-    check (bb);
+    check_block (bb);
 
   if (dump_file)
     m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
@@ -3463,6 +3890,10 @@ pass_waccess::execute (function *fun)
   disable_ranger (fun);
   m_ptr_qry.rvals = NULL;
 
+  m_bb_uids_set = NULL;
+
+  free_dominance_info (CDI_POST_DOMINATORS);
+  free_dominance_info (CDI_DOMINATORS);
   return 0;
 }
 
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-2.c b/gcc/testsuite/c-c++-common/Wuse-after-free-2.c
new file mode 100644
index 00000000000..598ecf595ea
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-2.c
@@ -0,0 +1,137 @@
+/* Verify that accessing freed objects by built-in functions is diagnosed.
+   { dg-do compile }
+   { dg-options "-Wall" }  */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+EXTERN_C void* realloc (void*, size_t);
+
+EXTERN_C void* memcpy (void*, const void*, size_t);
+EXTERN_C char* strcpy (char*, const char*);
+EXTERN_C size_t strlen (const char*);
+
+
+void sink (void*, ...);
+
+struct Member { char *p; char a[4]; };
+
+int nowarn_strcpy_memptr (struct Member *p)
+{
+  char *q = strcpy (p->p, p->a);
+  free (p);
+  return *q;
+}
+
+int nowarn_strlen_memptr (struct Member *p)
+{
+  const char *q = p->p;
+
+  free (p);
+
+  return strlen (q);
+}
+
+int warn_strlen_memptr (struct Member *p)
+{
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  return strlen (p->p);       // { dg-warning "-Wuse-after-free" }
+}
+
+int warn_strlen_memarray (struct Member *p)
+{
+  {
+    free (p);
+    return strlen (p->a);     // { dg-warning "-Wuse-after-free" }
+  }
+
+  {
+    char *q = p->a;
+
+    free (p);
+    return strlen (q);        // { dg-warning "-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void* nowarn_realloc_success (void *p)
+{
+  void *q = realloc (p, 7);
+  if (!q)
+    /* When realloc fails the original pointer remains valid.  */
+    return p;
+
+  return q;
+}
+
+void* warn_realloc_unchecked (void *p, int *moved)
+{
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  *moved = p != q;            // { dg-warning "-Wuse-after-free" }
+  return q;
+}
+
+void* nowarn_realloc_unchecked_copy (void *p1, void *p2, const void *s,
+				     int n, int *x)
+{
+  void *p3 = memcpy (p1, s, n);
+  void *p4 = realloc (p2, 7);
+  *x = p3 != p4;
+  return p4;
+}
+
+void* warn_realloc_unchecked_copy (void *p, const void *s, int n, int *moved)
+{
+  void *p2 = memcpy (p, s, n);
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  *moved = p2 != q;           // { dg-warning "-Wuse-after-free" }
+  return q;
+}
+
+void* warn_realloc_failed (void *p, int *moved)
+{
+  void *q = realloc (p, 7);   // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
+  if (q)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      *moved = p != q;        // { dg-warning "-Wuse-after-free" }
+      return q;
+    }
+
+  return p;
+}
+
+extern void *evp;
+
+void* warn_realloc_extern (void *p, int *moved)
+{
+  evp = realloc (p, 7);
+  if (evp)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      *moved = p != evp;      // { dg-warning "-Wuse-after-free" "escaped" }
+      return evp;
+    }
+
+  return p;                   // { dg-bogus "-Wuse-after-free" "safe use after realloc failure" { xfail *-*-* } }
+}
+
+struct A { void *p, *q; int moved; };
+
+void* warn_realloc_arg (struct A *p)
+{
+  p->q = realloc (p->p, 7);
+  if (p->q)
+    {
+      /* When realloc succeeds the original pointer is invalid.  */
+      p->moved = p->p != p->q;  // { dg-warning "-Wuse-after-free" "escaped" { xfail *-*-* } }
+      return p->q;
+    }
+
+  return p->p;
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-3.c b/gcc/testsuite/c-c++-common/Wuse-after-free-3.c
new file mode 100644
index 00000000000..0a2db1a16c8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-3.c
@@ -0,0 +1,83 @@
+/* Exercise -Wuse-after-free with user-defined deallocators.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+EXTERN_C void free (void *);
+EXTERN_C void* realloc (void *, size_t);
+
+typedef struct List { struct List *next; } List;
+
+// User-defined allocator/deallocator just like like realloc and free.
+extern                     void  list_free (List *);
+extern                     List* list_realloc (size_t, List *);
+extern A (list_realloc, 2) List* list_realloc (size_t, List *);
+extern A (list_free, 1)    List* list_realloc (size_t, List *);
+
+
+void sink (void *);
+
+extern int ei;
+extern List *elp, *elpa[];
+
+void nowarn_list_free (struct List *lp)
+{
+  {
+    list_free (lp);
+    lp = 0;
+    sink (lp);
+  }
+  {
+    list_free (elp);
+    elp = 0;
+    sink (elp);
+  }
+  {
+    list_free (elpa[0]);
+    elpa[0] = 0;
+    sink (elpa[0]);
+  }
+  {
+    void *vp = elpa[0];
+    list_free (elpa[0]);
+    sink (vp);
+  }
+  {
+    List *p = elpa[1];
+    if (ei & 1)
+      list_free (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    list_free (lp);
+    list_free (next);
+  }
+}
+
+
+void nowarn_list_free_list (List *head)
+{
+  for (List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      list_free (p);
+    }
+}
+
+void warn_list_free_list (List *head)
+{
+  List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    list_free (p);            // { dg-message "call to '\(void \)?list_free\(\\(List\\*\\)\)?'" "note" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-4.c b/gcc/testsuite/c-c++-common/Wuse-after-free-4.c
new file mode 100644
index 00000000000..686ba7e256c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-4.c
@@ -0,0 +1,102 @@
+/* Verify -Wuse-after-free=1 triggers only for unconditional uses and
+   not for equality expressions.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=1" } */
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void nowarn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void nowarn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* nowarn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void nowarn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],
+     c ? p <= q[1] : q[1][1],
+     c ? p > q[2] : q[2][2],
+     c ? p >= q[3] : q[3][3],
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify no warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data);   // no warning at level 1
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-5.c b/gcc/testsuite/c-c++-common/Wuse-after-free-5.c
new file mode 100644
index 00000000000..c6ff1f3fad2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-5.c
@@ -0,0 +1,103 @@
+/* Verify -Wuse-after-free=2 triggers for conditional as well as
+   unconditional uses but not for equality expressions.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=2" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-6.c b/gcc/testsuite/c-c++-common/Wuse-after-free-6.c
new file mode 100644
index 00000000000..581b1a0a024
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-6.c
@@ -0,0 +1,105 @@
+/* Verify -Wuse-after-free=2 triggers for conditional as well as
+   unconditional uses but not for equality expressions.  Same as
+   -Wuse-after-free-5.c but with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wuse-after-free=2" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  // PHI handling not fully implemented.
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" "pr??????" { xfail *-*-* } }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],
+     p != q[5]
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4],
+     c ? p != q[5] : q[5][5],
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free-7.c b/gcc/testsuite/c-c++-common/Wuse-after-free-7.c
new file mode 100644
index 00000000000..12bb6f24ea5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free-7.c
@@ -0,0 +1,103 @@
+/* Verify -Wuse-after-free=3 triggers for conditional and unconditional
+   uses in all expressions including equality.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wuse-after-free=3" } */
+
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void free (void*);
+
+void sink (void*);
+
+
+void warn_double_free (void *p)
+{
+  free (p);
+  free (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_double_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    free (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void warn_call_after_free (void *p)
+{
+  free (p);
+  sink (p);         // { dg-warning "pointer 'p' used" }
+}
+
+void warn_cond_call_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    sink (p);       // { dg-warning "pointer 'p' may be used" }
+}
+
+void* warn_return_after_free (void *p)
+{
+  free (p);
+  return p;         // { dg-warning "pointer 'p' used" }
+}
+
+void* warn_cond_return_after_free (void *p, int c)
+{
+  free (p);
+  if (c)
+    return p;       // { dg-warning "pointer 'p' may be used" }
+  return 0;
+}
+
+void warn_relational_after_free (char *p, char *q[])
+{
+  free (p);
+
+  int a[] =
+    {
+     p < q[0],      // { dg-warning "pointer 'p' used" }
+     p <= q[1],     // { dg-warning "pointer 'p' used" }
+     p > q[2],      // { dg-warning "pointer 'p' used" }
+     p >= q[3],     // { dg-warning "pointer 'p' used" }
+     p == q[4],     // { dg-warning "pointer 'p' used" }
+     p != q[5]      // { dg-warning "pointer 'p' used" }
+    };
+
+  sink (a);
+}
+
+void warn_cond_relational_after_free (char *p, char *q[], int c)
+{
+  free (p);
+
+  int a[] =
+    {
+     c ? p < q[0] : q[0][0],  // { dg-warning "pointer 'p' may be used" }
+     c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
+     c ? p > q[2] : q[2][2],  // { dg-warning "pointer 'p' may be used" }
+     c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
+     c ? p == q[4] : q[4][4], // { dg-warning "pointer 'p' may be used" }
+     c ? p != q[5] : q[5][5], // { dg-warning "pointer 'p' may be used" }
+    };
+
+  sink (a);
+}
+
+
+// Verify warning for the example in the manual.
+
+struct A { int refcount; void *data; };
+
+void release (struct A *p)
+{
+  int refcount = --p->refcount;
+  free (p);
+  if (refcount == 0)
+    free (p->data); // { dg-warning "pointer 'p' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wuse-after-free.c b/gcc/testsuite/c-c++-common/Wuse-after-free.c
new file mode 100644
index 00000000000..81bb7ff3841
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wuse-after-free.c
@@ -0,0 +1,164 @@
+/* Exercise basic cases of -Wuse-after-free without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* alloca (size_t);
+
+EXTERN_C void* calloc (size_t, size_t);
+EXTERN_C void* malloc (size_t);
+
+EXTERN_C void free (void*);
+
+
+void sink (void *);
+
+extern void* evp;
+extern void* evpa[];
+
+extern int ei;
+
+struct List { struct List *next; };
+
+void nowarn_free (void *vp, struct List *lp)
+{
+  {
+    free (vp);
+    vp = 0;
+    sink (vp);
+  }
+  {
+    free (evp);
+    evp = 0;
+    sink (evp);
+  }
+  {
+    free (evpa[0]);
+    evpa[0] = 0;
+    sink (evpa[0]);
+  }
+  {
+    void *vp = evpa[0];
+    free (evpa[1]);
+    sink (vp);
+  }
+  {
+    void *p = evpa[1];
+    if (ei & 1)
+      free (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    free (lp);
+    free (next);
+  }
+}
+
+void nowarn_free_arg (void *p, void *q)
+{
+  free (p);
+  if (q)
+    free (q);
+}
+
+void nowarn_free_extern (void)
+{
+  extern void *ep, *eq;
+  free (ep);
+  ep = eq;
+  free (ep);
+}
+
+void nowarn_free_assign (void)
+{
+  extern void *ep;
+  free (ep);
+  ep = 0;
+  free (ep);
+}
+
+#pragma GCC diagnostic push
+/* Verify that -Wuse-after-free works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wuse-after-free"
+
+void nowarn_double_free_suppressed (void *p)
+{
+  free (p);
+  free (p);
+}
+
+#pragma GCC diagnostic pop
+
+void warn_double_free_arg (void *p)
+{
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  // Verify exactly one warning is issued.
+  free (p);                   // { dg-warning "\\\-Wuse-after-free" }
+                              // { dg-bogus "\\\-Wuse-after-free" "duplicate warning" { target *-*-* } .-1 }
+
+}
+
+void warn_double_free_extern (void)
+{
+  /* GCC assumes free() clobbers global memory and the warning is
+     too simplistic to see through that assumption.  */
+  extern void *ep, *eq;
+  {
+    eq = ep;
+    free (ep);                // { dg-message "call to 'free'" "pr??????" { xfail *-*-* } }
+    free (eq);                // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void warn_deref_after_free (int *p, int i)
+{
+  int *q0 = p, *q1 = p + 1, *qi = p + i;
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  *p = 0;                     // { dg-warning "\\\-Wuse-after-free" }
+
+  *q0 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *q1 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *qi = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_array_ref_after_free (int *p, int i)
+{
+  free (p);                   // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+  p[i] = 0;                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void nowarn_free_list (struct List *head)
+{
+  for (struct List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      free (p);
+    }
+}
+
+void warn_free_list (struct List *head)
+{
+  struct List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    free (p);                 // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+}
+
+
+void warn_free (void *vp)
+{
+  {
+    free (vp);                // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
+    evp = vp;                 // { dg-warning "-Wuse-after-free" }
+    evpa[0] = vp;             // { dg-warning "-Wuse-after-free" }
+    evpa[1] = evp;
+  }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
new file mode 100644
index 00000000000..05c7feef5c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
@@ -0,0 +1,70 @@
+/* Verify that passing a pointer to a deallocation function that was
+   previously passed to a mismatched reallocation function is diagnosed
+   by -Wmismatched-dealloc (and not by some other warning).
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern "C"
+{
+  void free (void *);
+  void* realloc (void *, size_t);
+}
+
+// User-defined allocator/deallocator just like like realloc.
+                   int* int_realloc (size_t, int *);
+A (int_realloc, 2) int* int_realloc (size_t, int *);
+
+
+void sink (void *);
+
+
+void* warn_realloc_op_delete (void *p)
+{
+  void *q = realloc (p, 5);   // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  operator delete (p);        // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
+
+void* warn_realloc_op_delete_cond (void *p)
+{
+  void *q = realloc (p, 5);      // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  if (!q)
+    operator delete (p);         // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
+  return q;
+}
+
+void* warn_realloc_array_delete_char (char *p)
+{
+  char *q;
+  q = (char*)realloc (p, 7);  // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
+
+  if (!q)
+    delete[] (p);             // { dg-warning "'void operator delete \\\[]\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
+  return q;
+}
+
+
+int* warn_int_realloc_op_delete (int *p)
+{
+  int *q;
+  q = int_realloc (5, p);     // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
+
+  operator delete (p);        // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
+
+
+int* warn_int_realloc_free (int *p)
+{
+  int *q;
+  q = int_realloc (5, p);    // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
+
+  free (p);                   // { dg-warning "'void free\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
+  return q;
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wuse-after-free.C b/gcc/testsuite/g++.dg/warn/Wuse-after-free.C
new file mode 100644
index 00000000000..022bd8d39f9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wuse-after-free.C
@@ -0,0 +1,158 @@
+/* Exercise basic C++ only cases of -Wuse-after-free without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern "C" void free (void *);
+extern "C" void* realloc (void *, size_t);
+
+void sink (void *);
+
+extern void* evp;
+extern void* evpa[];
+
+extern int ei;
+
+struct List { struct List *next; };
+
+void nowarn_delete (void *vp, struct List *lp)
+{
+  {
+    operator delete (vp);
+    vp = 0;
+    sink (vp);
+  }
+  {
+    operator delete (evp);
+    evp = 0;
+    sink (evp);
+  }
+  {
+    operator delete (evpa[0]);
+    evpa[0] = 0;
+    sink (evpa[0]);
+  }
+  {
+    void *vp = evpa[0];
+    operator delete (evpa[0]);
+    sink (vp);
+  }
+  {
+    void *p = evpa[1];
+    if (ei & 1)
+      operator delete (p);
+    if (ei & 2)
+      sink (p);
+  }
+  {
+    struct List *next = lp->next;
+    operator delete (lp);
+    operator delete (next);
+  }
+}
+
+void nowarn_delete_arg (void *p, void *q)
+{
+  operator delete (p);
+  if (q)
+    operator delete (q);
+}
+
+void nowarn_delete_extern (void)
+{
+  extern void *ep, *eq;
+  operator delete (ep);
+  ep = eq;
+  operator delete (ep);
+}
+
+void nowarn_delete_assign (void)
+{
+  extern void *ep;
+  operator delete (ep);
+  ep = 0;
+  operator delete (ep);
+}
+
+void warn_double_delete_arg (void *p)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  operator delete (p);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_delete_free_arg (void *p)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  free (p);                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_free_delete_arg (void *p)
+{
+  free (p);                   // { dg-message "call to 'void free\\(void\\*\\)'" "note" }
+  operator delete (p);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_mismatched_double_delete_arg (void *p, void *q)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  operator delete[] (p);      // { dg-warning "\\\-Wuse-after-free" }
+
+  operator delete[] (q);      // { dg-message "call to 'void operator delete \\\[]\\(void\\*\\)'" "note" }
+  operator delete (q);        // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_double_delete_extern (void)
+{
+  /* GCC assumes operator delete() clobbers global memory and the warning is
+     too simplistic to see through that assumption.  */
+  extern void *ep, *eq;
+  {
+    eq = ep;
+    operator delete (ep);     // { dg-message "call to 'operator delete'" "pr??????" { xfail *-*-* } }
+    operator delete (eq);     // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
+  }
+}
+
+void warn_deref_after_delete (int *p, int i)
+{
+  int *q0 = p, *q1 = p + 1, *qi = p + i;
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  *p = 0;                     // { dg-warning "\\\-Wuse-after-free" }
+
+  *q0 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *q1 = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+  *qi = 0;                    // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void warn_array_ref_after_delete (int *p, int i)
+{
+  operator delete (p);        // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+  p[i] = 0;                   // { dg-warning "\\\-Wuse-after-free" }
+}
+
+void nowarn_delete_list (struct List *head)
+{
+  for (struct List *p = head, *q; p; p = q)
+    {
+      q = p->next;
+      operator delete (p);
+    }
+}
+
+void warn_delete_list (struct List *head)
+{
+  struct List *p = head;
+  for (; p; p = p->next)      // { dg-warning "\\\[-Wuse-after-free" }
+    operator delete (p);      // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+}
+
+void warn_delete (void *vp)
+{
+  {
+    operator delete (vp);     // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
+    evp = vp;                 // { dg-warning "-Wuse-after-free" }
+    evpa[0] = vp;             // { dg-warning "-Wuse-after-free" }
+    evpa[1] = evp;
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
index 21a5ea7c5da..c303d2f1775 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c
@@ -26,6 +26,7 @@ void dealloc (void*);
 A (dealloc) void* alloc (int);
 
 void sink (void*);
+void* source (void);
 
 void test_alloc_A (void)
 {
@@ -107,35 +108,35 @@ void test_realloc_A (void *ptr)
 }
 
 
-void test_realloc (void *ptr)
+void test_realloc (void)
 {
   extern void free (void*);
   extern void* realloc (void*, size_t);
 
   {
-    void *p = realloc (ptr, 1);
+    void *p = realloc (source (), 1);
     p = realloc_A (p, 2);
     __builtin_free (p);
   }
 
   {
-    void *p = realloc (ptr, 2);
+    void *p = realloc (source (), 2);
     p = realloc_A (p, 2);
     free (p);
   }
 
   {
-    void *p = realloc (ptr, 3);
+    void *p = realloc (source (), 3);
     free (p);
   }
 
   {
-    void *p = realloc (ptr, 4);
+    void *p = realloc (source (), 4);
     __builtin_free (p);
   }
 
   {
-    void *p = realloc (ptr, 5);         // { dg-message "returned from 'realloc'" }
+    void *p = realloc (source (), 5);   // { dg-message "returned from 'realloc'" }
     dealloc (p);                        // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
   }
 }
diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
index 5afcea39b5e..302900662ce 100644
--- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
+++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c
@@ -157,6 +157,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = realloc (p, 1);
     q = reallocarray (q, 2, 3);
     sink (q);
@@ -192,6 +193,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 7, 8);
     q = __builtin_realloc (q, 9);
     sink (q);
@@ -199,6 +201,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 7, 8);
     q = realloc (q, 9);
     sink (q);
@@ -206,6 +209,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 8, 9);
     q = reallocarray (q, 3, 4);
     sink (q);
@@ -213,6 +217,7 @@ void test_reallocarray (void *p)
   }
 
   {
+    p = source ();
     void *q = reallocarray (p, 9, 10);
     q = reallocarray (q, 3, 4);
     sink (q);
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-6.c b/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
index bf010c53607..e28057f9007 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-6.c
@@ -5,7 +5,7 @@
    -Walloc-larger-than=maximum.  */
 /* { dg-do compile } */
 /* { dg-require-effective-target alloca } */
-/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345" } */
+/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
 
 #define MAXOBJSZ  12345
 
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
index 3adde5c2270..6c26935211a 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c
@@ -4,7 +4,7 @@
    of the maximum specified by -Walloc-size-larger-than=maximum.  */
 /* { dg-do compile } */
 /* { dg-require-effective-target alloca } */
-/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345" } */
+/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
 
 #define SIZE_MAX   __SIZE_MAX__
 #define MAXOBJSZ   12345
diff --git a/libcpp/files.c b/libcpp/files.c
index c93a03c69ef..64989219ce0 100644
--- a/libcpp/files.c
+++ b/libcpp/files.c
@@ -553,12 +553,11 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
 		  {
 		    /* If *hash_slot is NULL, the above
 		       htab_find_slot_with_hash call just created the
-		       slot, but we aren't going to store there
-		       anything, so need to remove the newly created
-		       entry.  htab_clear_slot requires that it is
-		       non-NULL, so store there some non-NULL pointer,
-		       htab_clear_slot will overwrite it
-		       immediately.  */
+		       slot, but we aren't going to store there anything
+		       of use, so need to remove the newly created entry.
+		       htab_clear_slot requires that it is non-NULL, so
+		       store some non-NULL but valid pointer there,
+		       htab_clear_slot will immediately overwrite it.  */
 		    *hash_slot = file;
 		    htab_clear_slot (pfile->file_hash, hash_slot);
 		  }
@@ -582,7 +581,7 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
 		if (*hash_slot == NULL)
 		  {
 		    /* See comment on the above htab_clear_slot call.  */
-		    *hash_slot = file;
+		    *hash_slot = &hash_slot;
 		    htab_clear_slot (pfile->file_hash, hash_slot);
 		  }
 		return NULL;
diff --git a/libiberty/regex.c b/libiberty/regex.c
index 5531d877f0b..b6cb2320b56 100644
--- a/libiberty/regex.c
+++ b/libiberty/regex.c
@@ -30,6 +30,10 @@
   #pragma alloca
 #endif
 
+#if __GNUC__ >= 12
+#  pragma GCC diagnostic ignored "-Wuse-after-free"
+#endif
+
 #undef	_GNU_SOURCE
 #define _GNU_SOURCE
 

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

* [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2021-11-01 22:18 ` [PATCH 2/2] add -Wdangling-pointer [PR #63272] Martin Sebor
  2021-11-02  7:40   ` Eric Gallager
@ 2021-11-30 22:55   ` Martin Sebor
  2021-12-07  0:51     ` PING " Martin Sebor
  1 sibling, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-11-30 22:55 UTC (permalink / raw)
  To: gcc-patches

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

Attached is a revision of this patch with adjustments for
the changes to the prerequisite patch 1 in the series and
a couple of minor simplifications and slightly improved
test coverage, rested on x86_64-linux.

On 11/1/21 4:18 PM, Martin Sebor wrote:
> Patch 2 in this series adds support for detecting the uses of
> dangling pointers: those to auto objects that have gone out of
> scope.  Like patch 1, to minimize false positives this detection
> is very simplistic.  However, thanks to the more deterministic
> nature of the problem (all local objects go out of scope) is able
> to detect more instances of it.  The approach I used is to simply
> search the IL for clobbers that dominate uses of pointers to
> the clobbered objects.  If such a use is found that's not
> followed by a clobber of the same object the warning triggers.
> Similar to -Wuse-after-free, the new -Wdangling-pointer option
> has multiple levels: level 1 to detect unconditional uses and
> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
> there is no use case for testing dangling pointers for
> equality, so there is no level 3.
> 
> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
> It found no problems outside of the GCC test suite.
> 
> As with the first patch in this series, the tests contain a number
> of xfails due to known limitations marked with pr??????.  I'll
> open bugs for them before committing the patch if I don't resolve
> them first in a followup.
> 
> Martin


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

Add -Wdangling-pointer [PR63272].
Resolves:

PR c/63272 - GCC should warn when using pointer to dead scoped variable within the same function

gcc/c-family/ChangeLog:

	PR c/63272
	* c.opt (-Wdangling-pointer): New option.

gcc/ChangeLog:

	PR c/63272
	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
	-Wdangling-pointer.
	* doc/invoke.texi (-Wdangling-pointer): Document new option.
	* gimple-ssa-isolate-paths.c (diag_returned_locals): Suppress
	warning after issuing it.
	* gimple-ssa-warn-access.cc (pass_waccess::clone): Set new member.
	(pass_waccess::check_pointer_uses): New function.
	(pass_waccess::gimple_call_return_arg): New function.
	(pass_waccess::gimple_call_return_arg_ref): New function.
	(pass_waccess::check_call_dangling): New function.
	(pass_waccess::check_dangling_uses): New function overloads.
	(pass_waccess::check_dangling_stores): New function.
	(pass_waccess::check_dangling_stores): New function.
	(pass_waccess::m_clobbers): New data member.
	(pass_waccess::m_func): New data member.
	(pass_waccess::m_run_number): New data member.
	(pass_waccess::m_check_dangling_p): New data member.
	(pass_waccess::check_alloca): Check m_early_checks_p.
	(pass_waccess::check_alloc_size_call): Same.
	(pass_waccess::check_strcat): Same.
	(pass_waccess::check_strncat): Same.
	(pass_waccess::check_stxcpy): Same.
	(pass_waccess::check_stxncpy): Same.
	(pass_waccess::check_strncmp): Same.
	(pass_waccess::check_memop_access): Same.
	(pass_waccess::check_read_access): Same.
	(pass_waccess::check_builtin): Call check_pointer_uses.
	(pass_waccess::warn_invalid_pointer): Add arguments.
	(is_auto_decl): New function.
	(pass_waccess::check_stmt): New function.
	(pass_waccess::check_block): Call check_stmt.
	(pass_waccess::execute): Call check_dangling_uses,
	check_dangling_stores.  Empty m_clobbers.
	* passes.def (pass_warn_access): Invoke pass two more times.

gcc/testsuite/ChangeLog:

	PR c/63272
	* g++.dg/warn/Wfree-nonheap-object-6.C: Disable valid warnings.
	* gcc.dg/uninit-pr50476.c: Expect a new warning.
	* c-c++-common/Wdangling-pointer-2.c: New test.
	* c-c++-common/Wdangling-pointer-3.c: New test.
	* c-c++-common/Wdangling-pointer-4.c: New test.
	* c-c++-common/Wdangling-pointer-5.c: New test.
	* c-c++-common/Wdangling-pointer.c: New test.
	* gcc.dg/Wdangling-pointer-2.c: New test.
	* gcc.dg/Wdangling-pointer.c: New test.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index fb1abc0de4c..2e978ae9071 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -548,6 +548,14 @@ Wdangling-else
 C ObjC C++ ObjC++ Var(warn_dangling_else) Warning LangEnabledBy(C ObjC C++ ObjC++,Wparentheses)
 Warn about dangling else.
 
+Wdangling-pointer
+C ObjC C++ LTO ObjC++ Alias(Wdangling-pointer=, 2, 0) Warning
+Warn for uses of pointers to auto variables whose lifetime has ended.
+
+Wdangling-pointer=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2)
+Warn for uses of pointers to auto variables whose lifetime has ended.
+
 Wdate-time
 C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning
 Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage.
diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c
index 921e7ab7423..3b1e37a6836 100644
--- a/gcc/diagnostic-spec.c
+++ b/gcc/diagnostic-spec.c
@@ -99,6 +99,7 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt)
 	m_bits = NW_UNINIT;
       break;
 
+    case OPT_Wdangling_pointer_:
     case OPT_Wreturn_local_addr:
     case OPT_Wuse_after_free_:
       m_bits = NW_DANGLING;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 46bc8046436..dc44eadb36a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -341,7 +341,8 @@ Objective-C and Objective-C++ Dialects}.
 -Wchar-subscripts @gol
 -Wclobbered  -Wcomment @gol
 -Wconversion  -Wno-coverage-mismatch  -Wno-cpp @gol
--Wdangling-else  -Wdate-time @gol
+-Wdangling-else  -Wdangling-pointer  -Wdangling-pointer=@var{n}  @gol
+-Wdate-time @gol
 -Wno-deprecated  -Wno-deprecated-declarations  -Wno-designated-init @gol
 -Wdisabled-optimization @gol
 -Wno-discarded-array-qualifiers  -Wno-discarded-qualifiers @gol
@@ -5691,6 +5692,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wcatch-value @r{(C++ and Objective-C++ only)}  @gol
 -Wchar-subscripts  @gol
 -Wcomment  @gol
+-Wdangling-pointer=2  @gol
 -Wduplicate-decl-specifier @r{(C and Objective-C only)} @gol
 -Wenum-compare @r{(in C/ObjC; this is on by default in C++)} @gol
 -Wformat   @gol
@@ -8564,6 +8566,50 @@ looks like this:
 
 This warning is enabled by @option{-Wparentheses}.
 
+@item -Wdangling-pointer
+@itemx -Wdangling-pointer=@var{n}
+@opindex Wdangling-pointer
+@opindex Wno-dangling-pointer
+Warn about uses of pointers to objects with automatic storage duration after
+their lifetime has ended.  This includes local variables declared in nested
+blocks and compound literals.
+
+@table @gcctabopt
+@item -Wdangling-pointer=1
+At level 1 the warning diagnoses only unconditional uses of dangling pointers.
+For example
+@smallexample
+int f (int c1, int c2, x)
+@{
+  char *p = strchr ((char[])@{ c1, c2 @}, c3);
+  return p ? *p : 'x';   // warning: dangling pointer to a compound literal
+@}
+@end smallexample
+
+@item -Wdangling-pointer=2
+At level 2, in addition to unconditional uses the warning also diagnoses
+conditional uses of dangling pointers.
+
+For example, because the array @var{a} in the following function is out of
+scope when the pointer @var{s} that was set to point is used, the warning
+triggers at this level.
+
+@smallexample
+void f (char *s)
+@{
+  if (!s)
+    @{
+      char a[12] = "tmpname";
+      s = a;
+    @}
+  strcat (s, ".tmp");   // warning: dangling pointer to a may be used
+  ...
+@}
+@end smallexample
+@end table
+
+@option{-Wdangling-pointer=2} is included in @option{-Wall}.
+
 @item -Wdate-time
 @opindex Wdate-time
 @opindex Wno-date-time
diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index e396266088f..7ea34df86f6 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -2066,10 +2066,12 @@ class pass_waccess : public gimple_opt_pass
 
   ~pass_waccess ();
 
-  opt_pass *clone () { return new pass_waccess (m_ctxt); }
+  opt_pass *clone ();
 
   virtual bool gate (function *);
 
+  void set_pass_param (unsigned, bool);
+
   virtual unsigned int execute (function *);
 
 private:
@@ -2086,6 +2088,9 @@ private:
   /* Check a call to an ordinary function for invalid accesses.  */
   bool check_call_access (gcall *);
 
+  /* Check a non-call statement.  */
+  void check_stmt (gimple *);
+
   /* Check statements in a basic block.  */
   void check_block (basic_block);
 
@@ -2107,26 +2112,41 @@ private:
   void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *);
 
   /* Check for uses of indeterminate pointers.  */
-  void check_pointer_uses (gimple *, tree);
+  void check_pointer_uses (gimple *, tree, tree = NULL_TREE, bool = false);
 
   /* Return the argument that a call returns.  */
   tree gimple_call_return_arg (gcall *);
+  tree gimple_call_return_arg_ref (gcall *);
+
+  /* Check a call for uses of a dangling pointer arguments.  */
+  void check_call_dangling (gcall *);
+
+  /* Check uses of a dangling pointer or those derived from it.  */
+  void check_dangling_uses (tree, tree, bool = false);
+  void check_dangling_uses ();
+  void check_dangling_stores ();
+  void check_dangling_stores (basic_block, hash_set<tree> &, auto_bitmap &);
 
-  void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
+  void warn_invalid_pointer (tree, gimple *, gimple *, tree, bool, bool = false);
 
   /* Return true if use follows an invalidating statement.  */
-  bool use_after_inval_p (gimple *, gimple *);
+  bool use_after_inval_p (gimple *, gimple *, bool = false);
 
   /* A pointer_query object and its cache to store information about
      pointers and their targets in.  */
   pointer_query m_ptr_qry;
   pointer_query::cache_type m_var_cache;
-
+  /* Mapping from DECLs and their clobber statements in the function.  */
+  hash_map<tree, gimple *> m_clobbers;
   /* A bit is set for each basic block whose statements have been assigned
      valid UIDs.  */
   bitmap m_bb_uids_set;
   /* The current function.  */
   function *m_func;
+  /* True to run checks for uses of dangling pointers.  */
+  bool m_check_dangling_p;
+  /* True to run checks early on in the optimization pipeline.  */
+  bool m_early_checks_p;
 };
 
 /* Construct the pass.  */
@@ -2135,9 +2155,20 @@ pass_waccess::pass_waccess (gcc::context *ctxt)
   : gimple_opt_pass (pass_data_waccess, ctxt),
     m_ptr_qry (NULL, &m_var_cache),
     m_var_cache (),
+    m_clobbers (),
     m_bb_uids_set (),
-    m_func ()
+    m_func (),
+    m_check_dangling_p (),
+    m_early_checks_p ()
+{
+}
+
+/* Return a copy of the pass with RUN_NUMBER one greater than THIS.  */
+
+opt_pass*
+pass_waccess::clone ()
 {
+  return new pass_waccess (m_ctxt);
 }
 
 /* Release pointer_query cache.  */
@@ -2147,6 +2178,14 @@ pass_waccess::~pass_waccess ()
   m_ptr_qry.flush_cache ();
 }
 
+void
+pass_waccess::set_pass_param (unsigned int n, bool early)
+{
+  gcc_assert (n == 0);
+
+  m_early_checks_p = early;
+}
+
 /* Return true when any checks performed by the pass are enabled.  */
 
 bool
@@ -2335,6 +2374,9 @@ maybe_warn_alloc_args_overflow (gimple *stmt, const tree args[2],
 void
 pass_waccess::check_alloca (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if ((warn_vla_limit >= HOST_WIDE_INT_MAX
        && warn_alloc_size_limit < warn_vla_limit)
       || (warn_alloca_limit >= HOST_WIDE_INT_MAX
@@ -2356,6 +2398,13 @@ pass_waccess::check_alloca (gcall *stmt)
 void
 pass_waccess::check_alloc_size_call (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
+  if (gimple_call_num_args (stmt) < 1)
+    /* Avoid invalid calls to functions without a prototype.  */
+    return;
+
   tree fndecl = gimple_call_fndecl (stmt);
   if (fndecl && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
     {
@@ -2408,6 +2457,9 @@ pass_waccess::check_alloc_size_call (gcall *stmt)
 void
 pass_waccess::check_strcat (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if (!warn_stringop_overflow && !warn_stringop_overread)
     return;
 
@@ -2433,6 +2485,9 @@ pass_waccess::check_strcat (gcall *stmt)
 void
 pass_waccess::check_strncat (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   if (!warn_stringop_overflow && !warn_stringop_overread)
     return;
 
@@ -2502,6 +2557,9 @@ pass_waccess::check_strncat (gcall *stmt)
 void
 pass_waccess::check_stxcpy (gcall *stmt)
 {
+  if (m_early_checks_p)
+    return;
+
   tree dst = call_arg (stmt, 0);
   tree src = call_arg (stmt, 1);
 
@@ -2540,7 +2598,7 @@ pass_waccess::check_stxcpy (gcall *stmt)
 void
 pass_waccess::check_stxncpy (gcall *stmt)
 {
-  if (!warn_stringop_overflow)
+  if (m_early_checks_p || !warn_stringop_overflow)
     return;
 
   tree dst = call_arg (stmt, 0);
@@ -2564,7 +2622,7 @@ pass_waccess::check_stxncpy (gcall *stmt)
 void
 pass_waccess::check_strncmp (gcall *stmt)
 {
-  if (!warn_stringop_overread)
+  if (m_early_checks_p || !warn_stringop_overread)
     return;
 
   tree arg1 = call_arg (stmt, 0);
@@ -2669,6 +2727,9 @@ pass_waccess::check_strncmp (gcall *stmt)
 void
 pass_waccess::check_memop_access (gimple *stmt, tree dest, tree src, tree size)
 {
+  if (m_early_checks_p)
+    return;
+
   /* For functions like memset and memcpy that operate on raw memory
      try to determine the size of the largest source and destination
      object using type-0 Object Size regardless of the object size
@@ -2690,7 +2751,7 @@ pass_waccess::check_read_access (gimple *stmt, tree src,
 				 tree bound /* = NULL_TREE */,
 				 int ost /* = 1 */)
 {
-  if (!warn_stringop_overread)
+  if (m_early_checks_p || !warn_stringop_overread)
     return;
 
   if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound)))
@@ -2818,11 +2879,12 @@ pass_waccess::check_builtin (gcall *stmt)
 
     case BUILT_IN_FREE:
     case BUILT_IN_REALLOC:
-      {
-	tree arg = call_arg (stmt, 0);
-	if (TREE_CODE (arg) == SSA_NAME)
-	  check_pointer_uses (stmt, arg);
-      }
+      if (!m_early_checks_p)
+	{
+	  tree arg = call_arg (stmt, 0);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    check_pointer_uses (stmt, arg);
+	}
       return true;
 
     case BUILT_IN_GETTEXT:
@@ -3445,16 +3507,64 @@ pass_waccess::maybe_check_dealloc_call (gcall *call)
 
 /* Return true if either USE_STMT's basic block (that of a pointer's use)
    is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
-   or if they're in the same block, USE_STMT follows INVAL_STMT.  */
+   which is either a clobber or a deallocation call), or if they're in
+   the same block, USE_STMT follows INVAL_STMT.  */
 
 bool
-pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt)
+pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
+				 bool last_block /* = false */)
 {
+  tree clobvar =
+    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE;
+
   basic_block inval_bb = gimple_bb (inval_stmt);
   basic_block use_bb = gimple_bb (use_stmt);
 
   if (inval_bb != use_bb)
-    return dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb);
+    {
+      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
+	return true;
+
+      if (!clobvar || !last_block)
+	return false;
+
+      /* Proceed only when looking for uses of dangling pointers.  */
+      auto gsi = gsi_for_stmt (use_stmt);
+
+      auto_bitmap visited;
+
+      /* A use statement in the last basic block in a function or one that
+	 falls through to it is after any other prior clobber of the used
+	 variable unless it's followed by a clobber of the same variable. */
+      basic_block bb = use_bb;
+      while (bb != inval_bb
+	     && single_succ_p (bb)
+	     && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
+	{
+	  if (!bitmap_set_bit (visited, bb->index))
+	    /* Avoid cycles. */
+	    return true;
+
+	  for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gimple_clobber_p (stmt))
+		{
+		  if (clobvar == gimple_assign_lhs (stmt))
+		    /* The use is followed by a clobber.  */
+		    return false;
+		}
+	    }
+
+	  bb = single_succ (bb);
+	  gsi = gsi_start_bb (bb);
+	}
+
+      /* The use is one of a dangling pointer if a clobber of the variable
+	 [the pointer points to] has not been found before the function exit
+	 point.  */
+      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
+    }
 
   if (bitmap_set_bit (m_bb_uids_set, inval_bb->index))
     /* The first time this basic block is visited assign increasing ids
@@ -3474,12 +3584,15 @@ pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt)
 
 /* Issue a warning for the USE_STMT of pointer PTR rendered invalid
    by INVAL_STMT.  PTR may be null when it's been optimized away.
-   MAYBE is true to issue the "maybe" kind of warning.  EQUALITY is
-   true when the pointer is used in an equality expression.  */
+   When nonnull, CALLEE is the deallocation function that rendered
+   the pointer dangling.  Otherwise, VAR is the auto variable or
+   compound literal whose lifetime's rended it dangling.  MAYBE is
+   true to issue the "maybe" kind of warning.  EQUALITY is true when
+   the pointer is used in an equality expression.  */
 
 void
 pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
-				    gimple *inval_stmt,
+				    gimple *inval_stmt, tree var,
 				    bool maybe,
 				    bool equality /* = false */)
 {
@@ -3491,7 +3604,7 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
   location_t use_loc = gimple_location (use_stmt);
   if (use_loc == UNKNOWN_LOCATION)
     {
-      use_loc = cfun->function_end_locus;
+      use_loc = m_func->function_end_locus;
       if (!ptr)
 	/* Avoid issuing a warning with no context other than
 	   the function.  That would make it difficult to debug
@@ -3525,6 +3638,52 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
 	}
       return;
     }
+
+  if ((maybe && warn_dangling_pointer < 2)
+      || warning_suppressed_p (use_stmt, OPT_Wdangling_pointer_))
+    return;
+
+  if (DECL_NAME (var))
+    {
+      if ((ptr
+	   && warning_at (use_loc, OPT_Wdangling_pointer_,
+			  (maybe
+			   ? G_("dangling pointer %qE to %qD may be used")
+			   : G_("using dangling pointer %qE to %qD")),
+			  ptr, var))
+	  || (!ptr
+	      && warning_at (use_loc, OPT_Wdangling_pointer_,
+			     (maybe
+			      ? G_("dangling pointer to %qD may be used")
+			      : G_("using a dangling pointer to %qD")),
+			     var)))
+	inform (DECL_SOURCE_LOCATION (var),
+		"%qD declared here", var);
+      suppress_warning (use_stmt, OPT_Wdangling_pointer_);
+      return;
+    }
+
+  if ((ptr
+       && warning_at (use_loc, OPT_Wdangling_pointer_,
+		      (maybe
+		       ? G_("dangling pointer %qE to a compound literal "
+			    "may be used")
+		       : G_("using dangling pointer %qE to a compound "
+			    "literal")),
+		      ptr, var))
+      || (!ptr
+	  && warning_at (use_loc, OPT_Wdangling_pointer_,
+			 (maybe
+			  ? G_("dangling pointer to a compound literal "
+			       "may be used")
+			  : G_("using a dangling pointer to a compound "
+			       "literal")),
+			 var)))
+    {
+      inform (DECL_SOURCE_LOCATION (var),
+	      "compound literal defined here");
+      suppress_warning (use_stmt, OPT_Wdangling_pointer_);
+    }
 }
 
 /* If STMT is a call to either the standard realloc or to a user-defined
@@ -3647,10 +3806,14 @@ pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
 
 /* For a STMT either a call to a deallocation function or a clobber, warn
    for uses of the pointer PTR it was called with (including its copies
-   or others derived from it by pointer arithmetic).  */
+   or others derived from it by pointer arithmetic).  If STMT is a clobber,
+   VAR is the decl of the clobbered variable.  When MAYBE is true use
+   a "maybe" form of diagnostic.  */
 
 void
-pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
+pass_waccess::check_pointer_uses (gimple *stmt, tree ptr,
+				  tree var /* = NULL_TREE */,
+				  bool maybe /* = false */)
 {
   gcc_assert (TREE_CODE (ptr) == SSA_NAME);
 
@@ -3733,18 +3896,25 @@ pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
 	  /* Warn if USE_STMT is dominated by the deallocation STMT.
 	     Otherwise, add the pointer to POINTERS so that the uses
 	     of any other pointers derived from it can be checked.  */
-	  if (use_after_inval_p (stmt, use_stmt))
+	  if (use_after_inval_p (stmt, use_stmt, check_dangling))
 	    {
-	      /* TODO: Handle PHIs but careful of false positives.  */
-	      if (gimple_code (use_stmt) != GIMPLE_PHI)
+	      if (gimple_code (use_stmt) == GIMPLE_PHI)
 		{
-		  basic_block use_bb = gimple_bb (use_stmt);
-		  bool this_maybe
-		    = !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb);
-		  warn_invalid_pointer (*use_p->use, use_stmt, stmt,
-					this_maybe, equality);
-		  continue;
+		  tree lhs = gimple_phi_result (use_stmt);
+		  if (TREE_CODE (lhs) == SSA_NAME)
+		    {
+		      pointers.safe_push (lhs);
+		      continue;
+		    }
 		}
+
+	      basic_block use_bb = gimple_bb (use_stmt);
+	      bool this_maybe
+		= (maybe
+		   || !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb));
+	      warn_invalid_pointer (*use_p->use, use_stmt, stmt, var,
+				    this_maybe, equality);
+	      continue;
 	    }
 
 	  if (is_gimple_assign (use_stmt))
@@ -3779,26 +3949,99 @@ pass_waccess::check_call (gcall *stmt)
   if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
     check_builtin (stmt);
 
-  if (tree callee = gimple_call_fndecl (stmt))
-    {
-      /* Check for uses of the pointer passed to either a standard
-	 or a user-defined deallocation function.  */
-      unsigned argno = fndecl_dealloc_argno (callee);
-      if (argno < (unsigned) call_nargs (stmt))
-	{
-	  tree arg = call_arg (stmt, argno);
-	  if (TREE_CODE (arg) == SSA_NAME)
-	    check_pointer_uses (stmt, arg);
-	}
-    }
+  if (!m_early_checks_p)
+    if (tree callee = gimple_call_fndecl (stmt))
+      {
+	/* Check for uses of the pointer passed to either a standard
+	   or a user-defined deallocation function.  */
+	unsigned argno = fndecl_dealloc_argno (callee);
+	if (argno < (unsigned) call_nargs (stmt))
+	  {
+	    tree arg = call_arg (stmt, argno);
+	    if (TREE_CODE (arg) == SSA_NAME)
+	      check_pointer_uses (stmt, arg);
+	  }
+      }
 
   check_call_access (stmt);
+  check_call_dangling (stmt);
+
+  if (m_early_checks_p)
+    return;
 
   maybe_check_dealloc_call (stmt);
   check_nonstring_args (stmt);
 }
 
 
+/* Return true of X is a DECL with automatic storage duration.  */
+
+static inline bool
+is_auto_decl (tree x)
+{
+  return DECL_P (x) && !DECL_EXTERNAL (x) && !TREE_STATIC (x);
+}
+
+/* Check non-call STMT for invalid accesses.  */
+
+void
+pass_waccess::check_stmt (gimple *stmt)
+{
+  if (m_check_dangling_p && gimple_clobber_p (stmt))
+    {
+      /* Ignore clobber statemts in blocks with exceptional edges.  */
+      basic_block bb = gimple_bb (stmt);
+      edge e = EDGE_PRED (bb, 0);
+      if (e->flags & EDGE_EH)
+	return;
+
+      tree var = gimple_assign_lhs (stmt);
+      m_clobbers.put (var, stmt);
+      return;
+    }
+
+  if (is_gimple_assign (stmt))
+    {
+      /* Clobbered compound literals can be revived.  Check for
+	 an assignment to one and remove it from M_CLOBBERS.  */
+      tree lhs = gimple_assign_lhs (stmt);
+      while (handled_component_p (lhs))
+	lhs = TREE_OPERAND (lhs, 0);
+
+      if (is_auto_decl (lhs))
+	m_clobbers.remove (lhs);
+      return;
+    }
+
+  if (greturn *ret = dyn_cast <greturn *> (stmt))
+    {
+      if (optimize && flag_isolate_erroneous_paths_dereference)
+	/* Avoid interfering with -Wreturn-local-addr (which runs only
+	   with optimization enabled).  */
+	return;
+
+      tree arg = gimple_return_retval (ret);
+      if (!arg || TREE_CODE (arg) != ADDR_EXPR)
+	return;
+
+      arg = TREE_OPERAND (arg, 0);
+      while (handled_component_p (arg))
+	arg = TREE_OPERAND (arg, 0);
+
+      if (!is_auto_decl (arg))
+	return;
+
+      gimple **pclobber = m_clobbers.get (arg);
+      if (!pclobber)
+	return;
+
+      if (!use_after_inval_p (*pclobber, stmt))
+	return;
+
+      warn_invalid_pointer (NULL_TREE, stmt, *pclobber, arg, false);
+    }
+}
+
 /* Check basic block BB for invalid accesses.  */
 
 void
@@ -3811,6 +4054,8 @@ pass_waccess::check_block (basic_block bb)
       gimple *stmt = gsi_stmt (si);
       if (gcall *call = dyn_cast <gcall *> (stmt))
 	check_call (call);
+      else
+	check_stmt (stmt);
     }
 }
 
@@ -3859,6 +4104,232 @@ pass_waccess::gimple_call_return_arg (gcall *call)
   return gimple_call_arg (call, argno);
 }
 
+/* Return the decl referenced by the argument that the call STMT to
+   a built-in function returns (including with an offset) or null if
+   it doesn't.  */
+
+tree
+pass_waccess::gimple_call_return_arg_ref (gcall *call)
+{
+  if (tree arg = gimple_call_return_arg (call))
+    {
+      access_ref aref;
+      if (m_ptr_qry.get_ref (arg, call, &aref, 0)
+	  && DECL_P (aref.ref))
+	return aref.ref;
+    }
+
+  return NULL_TREE;
+}
+
+/* Check for and diagnose all uses of the dangling pointer VAR to
+   the auto object DECL whose lifetime has ended.  */
+
+void
+pass_waccess::check_dangling_uses (tree var, tree decl, bool maybe /* = false */)
+{
+  if (!decl || !is_auto_decl (decl))
+    return;
+
+  gimple **pclob = m_clobbers.get (decl);
+  if (!pclob)
+    return;
+
+  check_pointer_uses (*pclob, var, decl, maybe);
+}
+
+/* Diagnose stores in BB and (recursively) its predecessors of the addresses
+   of local variables into nonlocal pointers that are left dangling after
+   the function returns.  BBS is a bitmap of basic blocks visited.  */
+
+void
+pass_waccess::check_dangling_stores (basic_block bb,
+				     hash_set<tree> &stores,
+				     auto_bitmap &bbs)
+{
+  if (!bitmap_set_bit (bbs, bb->index))
+    /* Avoid cycles. */
+    return;
+
+  /* Iterate backwards over the statements looking for a store of
+     the address of a local variable into a nonlocal pointer.  */
+  for (auto gsi = gsi_last_nondebug_bb (bb); ; gsi_prev_nondebug (&gsi))
+    {
+      gimple *stmt = gsi_stmt (gsi);
+      if (!stmt)
+	break;
+
+      if (is_gimple_call (stmt)
+	  && !(gimple_call_flags (stmt) & (ECF_CONST | ECF_PURE)))
+	/* Avoid looking before nonconst, nonpure calls since those might
+	   use the escaped locals.  */
+	return;
+
+      if (!is_gimple_assign (stmt) || gimple_clobber_p (stmt))
+	continue;
+
+      access_ref lhs_ref;
+      tree lhs = gimple_assign_lhs (stmt);
+      if (!m_ptr_qry.get_ref (lhs, stmt, &lhs_ref, 0))
+	continue;
+
+      if (is_auto_decl (lhs_ref.ref))
+	continue;
+
+      if (DECL_P (lhs_ref.ref))
+	{
+	  if (!POINTER_TYPE_P (TREE_TYPE (lhs_ref.ref))
+	      || lhs_ref.deref > 0)
+	    continue;
+	}
+      else if (TREE_CODE (lhs_ref.ref) == SSA_NAME)
+	{
+	  /* Avoid looking at or before stores into unknown objects.  */
+	  gimple *def_stmt = SSA_NAME_DEF_STMT (lhs_ref.ref);
+	  if (!gimple_nop_p (def_stmt))
+	    return;
+	}
+      else if (TREE_CODE (lhs_ref.ref) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (lhs_ref.ref, 0);
+	  if (TREE_CODE (arg) == SSA_NAME)
+	    {
+	      gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
+	      if (!gimple_nop_p (def_stmt))
+		return;
+	    }
+	}
+      else
+	continue;
+
+      if (stores.add (lhs_ref.ref))
+	continue;
+
+      access_ref rhs_ref;
+      tree rhs = gimple_assign_rhs1 (stmt);
+      if (!m_ptr_qry.get_ref (rhs, stmt, &rhs_ref, 0)
+	  || rhs_ref.deref != -1)
+	continue;
+
+      if (!is_auto_decl (rhs_ref.ref))
+	continue;
+
+      location_t loc = gimple_location (stmt);
+      if (warning_at (loc, OPT_Wdangling_pointer_,
+		      "storing the address of local variable %qD in %qE",
+		      rhs_ref.ref, lhs))
+	{
+	  location_t loc = DECL_SOURCE_LOCATION (rhs_ref.ref);
+	  inform (loc, "%qD declared here", rhs_ref.ref);
+
+	  if (DECL_P (lhs_ref.ref))
+	    loc = DECL_SOURCE_LOCATION (lhs_ref.ref);
+	  else if (EXPR_HAS_LOCATION (lhs_ref.ref))
+	    loc = EXPR_LOCATION (lhs_ref.ref);
+
+	  if (loc != UNKNOWN_LOCATION)
+	    inform (loc, "%qE declared here", lhs_ref.ref);
+	}
+    }
+
+  edge e;
+  edge_iterator ei;
+  FOR_EACH_EDGE (e, ei, bb->preds)
+    {
+      basic_block pred = e->src;
+      check_dangling_stores (pred, stores, bbs);
+    }
+}
+
+/* Diagnose stores of the addresses of local variables into nonlocal
+   pointers that are left dangling after the function returns.  */
+
+void
+pass_waccess::check_dangling_stores ()
+{
+  auto_bitmap bbs;
+  hash_set<tree> stores;
+  check_dangling_stores (EXIT_BLOCK_PTR_FOR_FN (m_func), stores, bbs);
+}
+
+/* Check for and diagnose uses of dangling pointers to auto objects
+   whose lifetime has ended.  */
+
+void
+pass_waccess::check_dangling_uses ()
+{
+  tree var;
+  unsigned i;
+  FOR_EACH_SSA_NAME (i, var, m_func)
+    {
+      if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+	continue;
+
+      /* For each SSA_NAME pointer VAR find the DECL it points to.
+	 If the DECL is a clobbered local variable, check to see
+	 if any of VAR's uses (or those of other pointers derived
+	 from VAR) happens after the clobber.  If so, warn.  */
+      tree decl = NULL_TREE;
+
+      gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+      if (is_gimple_assign (def_stmt))
+	{
+	  tree rhs = gimple_assign_rhs1 (def_stmt);
+	  if (TREE_CODE (rhs) == ADDR_EXPR)
+	    decl = TREE_OPERAND (rhs, 0);
+	}
+      else if (gcall *call = dyn_cast<gcall *>(def_stmt))
+	decl = gimple_call_return_arg_ref (call);
+      else if (gphi *phi = dyn_cast <gphi *>(def_stmt))
+	{
+	  unsigned nargs = gimple_phi_num_args (phi);
+	  for (unsigned i = 0; i != nargs; ++i)
+	    {
+	      access_ref aref;
+	      tree arg = gimple_phi_arg_def (phi, i);
+	      if (!m_ptr_qry.get_ref (arg, phi, &aref, 0)
+		  || (aref.deref == 0
+		      && POINTER_TYPE_P (TREE_TYPE (aref.ref))))
+		continue;
+	      check_dangling_uses (var, aref.ref, true);
+	    }
+	  continue;
+	}
+      else
+	continue;
+
+      check_dangling_uses (var, decl);
+    }
+}
+
+/* Check CALL arguments for dangling pointers (those that have been
+   clobbered) and warn if found.  */
+
+void
+pass_waccess::check_call_dangling (gcall *call)
+{
+  unsigned nargs = gimple_call_num_args (call);
+  for (unsigned i = 0; i != nargs; ++i)
+    {
+      tree arg = gimple_call_arg (call, i);
+      if (TREE_CODE (arg) != ADDR_EXPR)
+	continue;
+
+      arg = TREE_OPERAND (arg, 0);
+      if (!DECL_P (arg))
+	continue;
+
+      gimple **pclobber = m_clobbers.get (arg);
+      if (!pclobber)
+	continue;
+
+      if (!use_after_inval_p (*pclobber, call))
+	continue;
+
+      warn_invalid_pointer (NULL_TREE, call, *pclobber, arg, false);
+    }
+}
+
 /* Check function FUN for invalid accesses.  */
 
 unsigned
@@ -3871,6 +4342,15 @@ pass_waccess::execute (function *fun)
   m_ptr_qry.rvals = enable_ranger (fun);
   m_func = fun;
 
+  /* Check for dangling pointers in the earliest run of the pass.
+     The latest point -Wdanglinng-pointer should run is just before
+     loop unrolling which introduces uses after clobbers.  Most cases
+     can be detected without optimization; cases where the address of
+     the local variable is passed to and then returned from a user-
+     defined function before its lifetime ends and the returned pointer
+     becomes dangling depend on inlining.  */
+  m_check_dangling_p = m_early_checks_p;
+
   auto_bitmap bb_uids_set (&bitmap_default_obstack);
   m_bb_uids_set = bb_uids_set;
 
@@ -3880,6 +4360,12 @@ pass_waccess::execute (function *fun)
   FOR_EACH_BB_FN (bb, fun)
     check_block (bb);
 
+  if (m_check_dangling_p)
+    {
+      check_dangling_uses ();
+      check_dangling_stores ();
+    }
+
   if (dump_file)
     m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
 
@@ -3890,6 +4376,7 @@ pass_waccess::execute (function *fun)
   disable_ranger (fun);
   m_ptr_qry.rvals = NULL;
 
+  m_clobbers.empty ();
   m_bb_uids_set = NULL;
 
   free_dominance_info (CDI_POST_DOMINATORS);
diff --git a/gcc/passes.def b/gcc/passes.def
index 37ea0d318d1..edf9c5be6a0 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -63,6 +63,7 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_ubsan);
       NEXT_PASS (pass_nothrow);
       NEXT_PASS (pass_rebuild_cgraph_edges);
+      NEXT_PASS (pass_warn_access, /*early=*/true);
   POP_INSERT_PASSES ()
 
   NEXT_PASS (pass_local_optimization_passes);
@@ -201,6 +202,8 @@ along with GCC; see the file COPYING3.  If not see
 	 form if possible.  */
       NEXT_PASS (pass_object_sizes);
       NEXT_PASS (pass_post_ipa_warn);
+      /* Must run before loop unrolling.  */
+      NEXT_PASS (pass_warn_access, /*early=*/true);
       NEXT_PASS (pass_complete_unrolli);
       NEXT_PASS (pass_backprop);
       NEXT_PASS (pass_phiprop);
@@ -426,7 +429,7 @@ along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_harden_compares);
   NEXT_PASS (pass_cleanup_cfg_post_optimizing);
   NEXT_PASS (pass_warn_function_noreturn);
-  NEXT_PASS (pass_warn_access);
+  NEXT_PASS (pass_warn_access, /*early=*/false);
 
   NEXT_PASS (pass_expand);
 
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
new file mode 100644
index 00000000000..527e5e7b2c6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
@@ -0,0 +1,437 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise basic cases of -Wdangling-pointer with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-uninitialized -Wno-return-local-addr -ftrack-macro-expansion=0" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+#define NOIPA __attribute__ ((noipa))
+
+EXTERN_C void* alloca (size_t);
+EXTERN_C void* malloc (size_t);
+EXTERN_C void* memchr (const void*, int, size_t);
+EXTERN_C char* strchr (const char*, int);
+
+int sink (const void*, ...);
+#define sink(...) sink (0, __VA_ARGS__)
+
+
+NOIPA void nowarn_addr (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };
+    p = a;
+  }
+
+  // This is suspect but not a clear error.
+  sink (&p);
+}
+
+
+NOIPA char* nowarn_ptr (void)
+{
+  char *p;
+  sink (&p);
+  return p;
+}
+
+
+NOIPA char* nowarn_cond_ptr (void)
+{
+  // Distilled from a false positive in Glibc dlerror.c.
+  char *q;
+  if (sink (&q))
+    return q;
+
+  return 0;
+}
+
+
+NOIPA void nowarn_loop_ptr (int n, int *p)
+{
+  // Distilled from a false positive in Glibc td_thr_get_info.c.
+  for (int i = 0; i != 2; ++i)
+    {
+      int x;
+      sink (&x);
+      *p++ = x;
+    }
+
+  /* With the loop unrolled, Q is clobbered just before the call to
+     sink(), making it indistinguishable from passing it a pointer
+     to an out-of-scope variable.  Verify that the warning doesn't
+     suffer from false positives due to this.
+     int * q;
+     int * q.1_17;
+     int * q.1_26;
+
+     <bb 2>:
+     f (&q);
+     q.1_17 = q;
+     *p_5(D) = q.1_17;
+     q ={v} {CLOBBER};
+     f (&q);
+     q.1_26 = q;
+     MEM[(void * *)p_5(D) + 8B] = q.1_26;
+     q ={v} {CLOBBER};
+     return;
+  */
+}
+
+
+NOIPA void nowarn_intptr_t (void)
+{
+  intptr_t ip;
+  {
+    int a[] = { 1, 2, 3 };
+    ip = (intptr_t)a;
+  }
+
+  // Using an intptr_t is not diagnosed.
+  sink (0, ip);
+}
+
+
+NOIPA void nowarn_string_literal (void)
+{
+  const char *s;
+  {
+    s = "123";
+  }
+
+  sink (s);
+}
+
+
+NOIPA void nowarn_extern_array (int x)
+{
+  {
+    /* This is a silly sanity check.  */
+    extern int eia[];
+    int *p;
+    {
+      p = eia;
+    }
+    sink (p);
+  }
+}
+
+
+NOIPA void nowarn_static_array (int x)
+{
+  {
+    const char *s;
+    {
+      static const char sca[] = "123";
+      s = sca;
+    }
+
+    sink (s);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = sia;
+    }
+
+    sink (p);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = (const int*)memchr (sia, x, sizeof sia);
+    }
+
+    sink (p);
+  }
+}
+
+
+NOIPA void nowarn_alloca (unsigned n)
+{
+  {
+    char *p;
+    {
+      p = (char*)alloca (n);
+    }
+    sink (p);
+  }
+  {
+    int *p;
+    {
+      p = (int*)alloca (n * sizeof *p);
+      sink (p);
+    }
+    sink (p);
+  }
+  {
+    long *p;
+    {
+      p = (long*)alloca (n * sizeof *p);
+      sink (p);
+      p = p + 1;
+    }
+    sink (p);
+  }
+}
+
+
+#pragma GCC diagnostic push
+/* Verify that -Wdangling-pointer works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wdangling-pointer"
+
+
+NOIPA void* nowarn_return_local_addr (void)
+{
+  int a[] = { 1, 2, 3 };
+  int *p = a;
+
+  /* This is a likely bug but it's not really one of using a dangling
+     pointer but rather of returning the address of a local variable
+     which is diagnosed by -Wreturn-local-addr.  */
+  return p;
+}
+
+NOIPA void* warn_return_local_addr (void)
+{
+  int *p = 0;
+  {
+    int a[] = { 1, 2, 3 };
+    sink (a);
+    p = a;
+  }
+
+  /* Unlike the above case, here the pointer is dangling when it's
+     used.  */
+  return p;                   // { dg-warning "using dangling pointer 'p' to 'a'" "pr??????" { xfail *-*-* } }
+}
+
+
+NOIPA void* nowarn_return_alloca (int n)
+{
+  int *p = (int*)alloca (n);
+  sink (p);
+
+  /* This is a likely bug but it's not really one of using a dangling
+     pointer but rather of returning the address of a local variable
+     which is diagnosed by -Wreturn-local-addr.  */
+  return p;
+}
+
+
+NOIPA void nowarn_scalar_call_ignored (void *vp)
+{
+  int *p;
+  {
+    int i;
+    p = &i;
+  }
+  sink (p);
+}
+
+#pragma GCC diagnostic pop
+
+NOIPA void warn_scalar_call (void)
+{
+  int *p;
+  {
+    int i;                    // { dg-message "'i' declared" "note" }
+    p = &i;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'i'" "array" }
+}
+
+
+NOIPA void warn_array_call (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" }
+}
+
+
+NOIPA void* warn_array_return (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+
+  return p;                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" }
+}
+
+
+NOIPA void warn_pr63272_c1 (int i)
+{
+  int *p = 0;
+
+  if (i)
+    {
+      int k = i;              // { dg-message "'k' declared" "note" }
+      p = &k;
+    }
+
+  sink (p ? *p : 0);          // { dg-warning "dangling pointer 'p' to 'k' may be used" }
+}
+
+
+NOIPA void warn_pr63272_c4 (void)
+{
+  int *p = 0;
+
+  {
+    int b;                    // { dg-message "'b' declared" "note" }
+    p = &b;
+  }
+
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'b'" "scalar" }
+}
+
+
+NOIPA void warn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+}
+
+
+NOIPA void warn_cond_else (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int *a = (int*)malloc (n);
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 2, 3 };
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
+
+
+NOIPA void warn_cond_if_else (int i)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 3, 4 };      // { dg-message "'b' declared" "pr??????" { xfail *-*-* } }
+     sink (b);
+     p = b;
+   }
+
+  /* With a PHI with more than invalid argument, only one use is diagnosed
+     because after the first diagnostic the code suppresses subsequent
+     ones for the same use.  This needs to be fixed.  */
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+                              // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 }
+}
+
+
+NOIPA void nowarn_gcc_i386 (int i)
+{
+  // Regression test reduced from gcc's i386.c.
+  char a[32], *p;
+
+  if (i != 1)
+    p = a;
+  else
+    p = 0;
+
+  if (i == 2)
+    sink (p);
+  else
+    {
+      if (p)
+	{
+	  sink (p);
+	  return;
+	}
+      sink (p);
+    }
+}
+
+
+NOIPA void warn_memchr (char c1, char c2, char c3, char c4)
+{
+  char *p = 0;
+  {
+    char a[] = { c1, c2, c3 };// { dg-message "'a' declared" "note" }
+    p = (char*)memchr (a, c4, 3);
+    if (!p)
+      return;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" }
+}
+
+
+NOIPA void warn_strchr (char c1, char c2, char c3, char c4)
+{
+  char *p = 0;
+  {
+    char a[] = { c1, c2, c3 }; // { dg-message "'a' declared" "note" }
+    p = (char*)strchr (a, c4);
+    if (!p)
+      return;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" }
+}
+
+
+static inline int* return_arg (int *p)
+{
+  return p;
+}
+
+NOIPA void warn_inline (int i1, int i2, int i3)
+{
+  int *p;
+  {
+    int a[] = { i1, i2, i3 }; // { dg-message "'a' declared" "note" }
+    p = return_arg (a);
+  }
+
+  sink (p);                   // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "inline" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
new file mode 100644
index 00000000000..d2f8f432eba
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
@@ -0,0 +1,64 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise conditional uses dangling pointers with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-maybe-uninitialized" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* memcpy (void*, const void*, size_t);
+
+void sink (const void*, ...);
+
+char* nowarn_conditional (char *s)
+{
+  // Reduced from Glibc's tmpnam.c.
+  extern char a[5];
+  char b[5];
+  char *p = s ? s : b;
+
+  sink (p);
+
+  if (s == 0)
+    return a;
+
+  return s;
+}
+
+
+char* nowarn_conditional_memcpy (char *s)
+{
+  // Reduced from Glibc's tmpnam.c.
+  extern char a[5];
+  char b[5];
+  char *p = s ? s : b;
+
+  sink (p);
+
+  if (s == 0)
+    return (char*)memcpy (a, p, 5);
+
+  return s;
+}
+
+
+int warn_conditional_block (int i)
+{
+  int *p;
+  if (i)
+  {
+    int a[] = { 1, 2, 3 };
+    p = &a[i];
+  }
+  else
+    p = &i;
+
+  return *p;        // { dg-warning "dangling pointer \('p' \)to 'a' may be used" }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
new file mode 100644
index 00000000000..e57e66f8336
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
@@ -0,0 +1,73 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise -Wdangling-pointer for VLAs.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */
+
+void sink (void*, ...);
+
+void nowarn_vla (int n)
+{
+  {
+    int vla1[n];
+    int *p1 = vla1;
+    sink (p1);
+
+    {
+      int vla2[n];
+      int *p2 = vla2;
+      sink (p1, p2);
+
+      {
+	int vla3[n];
+	int *p3 = vla3;
+	sink (p1, p2, p3);
+      }
+      sink (p1, p2);
+    }
+    sink (p1);
+  }
+}
+
+void warn_one_vla (int n)
+{
+  int *p;
+  {
+    int vla[n];               // { dg-message "'vla' declared" "pr??????" { xfail *-*-* } }
+    p = vla;
+  }
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla'" "vla" { xfail *-*-* } }
+}
+
+
+void warn_two_vlas_same_block (int n)
+{
+  int *p, *q;
+  {
+    int vla1[n];              // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } }
+    int vla2[n];              // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } }
+    p = vla1;
+    q = vla2;
+  }
+
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } }
+  sink (q);                   // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } }
+}
+
+
+void warn_two_vlas_in_series (int n)
+{
+  int *p;
+  {
+    int vla1[n];              // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } }
+    p = vla1;
+  }
+  sink (p);                   // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } }
+
+  int *q;
+  {
+    int vla2[n];              // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } }
+    q = vla2;
+  }
+  sink (q);                   // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } }
+}
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
new file mode 100644
index 00000000000..23d37960fad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
@@ -0,0 +1,79 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise -Wdangling-pointer for VLAs.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */
+
+void* sink (void*, ...);
+
+extern void *evp;
+
+void nowarn_store_extern_call (void)
+{
+  int x;
+  evp = &x;
+  sink (0);
+}
+
+void nowarn_store_extern_ovrwrite (void)
+{
+  int x;
+  evp = &x;
+  evp = 0;
+}
+
+void nowarn_store_extern_store (void)
+{
+  int x;
+  void **p = (void**)sink (&evp);
+  evp = &x;
+  *p = 0;
+}
+
+
+void warn_store_extern (void)
+{
+  extern void *evp1;  // { dg-message "'evp1' declared here" }
+  int x;              // { dg-message "'x' declared here" }
+  evp1 = &x;          // { dg-warning "storing the address of local variable 'x' in 'evp1'" }
+}
+
+
+void nowarn_store_arg_call (void **vpp)
+{
+  int x;
+  *vpp = &x;
+  sink (0);
+}
+
+void nowarn_store_arg_ovrwrite (void **vpp)
+{
+  int x;
+  *vpp = &x;
+  *vpp = 0;
+}
+
+void nowarn_store_arg_store (void **vpp)
+{
+  int x;
+  void **p = (void**)sink (0);
+  *vpp = &x;
+  *p = 0;
+}
+
+void* nowarn_store_arg_store_arg (void **vpp1, void **vpp2)
+{
+  int x;
+  void **p = (void**)sink (0);
+  *vpp1 = &x;         // warn here?
+  *vpp2 = 0;          // might overwrite *vpp1
+  return p;
+}
+
+void warn_store_arg (void **vpp)
+{
+  int x;              // { dg-message "'x' declared here" }
+  *vpp = &x;          // { dg-warning "storing the address of local variable 'x' in '\\*vpp'" }
+}
+
+
diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer.c b/gcc/testsuite/c-c++-common/Wdangling-pointer.c
new file mode 100644
index 00000000000..a65404f05c2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wdangling-pointer.c
@@ -0,0 +1,414 @@
+/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped
+   variable within the same function
+   Exercise basic cases of -Wdangling-pointer without optimization.
+   { dg-do compile }
+   { dg-options "-O0 -Wall -Wno-uninitialized -ftrack-macro-expansion=0" } */
+
+typedef __INTPTR_TYPE__ intptr_t;
+typedef __SIZE_TYPE__   size_t;
+
+#if __cplusplus
+#  define EXTERN_C extern "C"
+#else
+#  define EXTERN_C extern
+#endif
+
+EXTERN_C void* alloca (size_t);
+EXTERN_C void* malloc (size_t);
+EXTERN_C void* memchr (const void*, int, size_t);
+EXTERN_C char* strchr (const char*, int);
+
+int sink (const void*, ...);
+#define sink(...) sink (0, __VA_ARGS__)
+
+
+void nowarn_addr (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };
+    p = a;
+  }
+
+  // This is suspect but not a clear error.
+  sink (&p);
+}
+
+
+char* nowarn_ptr (void)
+{
+  char *p;
+  sink (&p);
+  return p;
+}
+
+
+char* nowarn_cond_ptr (void)
+{
+  // Distilled from a false positive in Glibc dlerror.c.
+  char *q;
+  if (sink (&q))
+    return q;
+
+  return 0;
+}
+
+
+void nowarn_loop_ptr (int n, int *p)
+{
+  // Distilled from a false positive in Glibc td_thr_get_info.c.
+  for (int i = 0; i != 2; ++i)
+    {
+      int x;
+      sink (&x);
+      *p++ = x;
+    }
+}
+
+
+void nowarn_intptr_t (void)
+{
+  intptr_t ip;
+  {
+    int a[] = { 1, 2, 3 };
+    ip = (intptr_t)a;
+  }
+
+  // Using an intptr_t is not diagnosed.
+  sink (0, ip);
+}
+
+
+void nowarn_string_literal (void)
+{
+  const char *s;
+  {
+    s = "123";
+  }
+
+  sink (s);
+}
+
+
+void nowarn_extern_array (int x)
+{
+  {
+    /* This is a silly sanity check.  */
+    extern int eia[];
+    int *p;
+    {
+      p = eia;
+    }
+    sink (p);
+  }
+}
+
+
+void nowarn_static_array (int x)
+{
+  {
+    const char *s;
+    {
+      static const char sca[] = "123";
+      s = sca;
+    }
+
+    sink (s);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = sia;
+    }
+
+    sink (p);
+  }
+  {
+    const int *p;
+    {
+      static const int sia[] = { 1, 2, 3 };
+      p = (const int*)memchr (sia, x, sizeof sia);
+    }
+
+    sink (p);
+  }
+}
+
+
+void nowarn_alloca (unsigned n)
+{
+  {
+    char *p;
+    {
+      p = (char*)alloca (n);
+    }
+    sink (p);
+  }
+  {
+    int *p;
+    {
+      p = (int*)alloca (n * sizeof *p);
+      sink (p);
+    }
+    sink (p);
+  }
+  {
+    long *p;
+    {
+      p = (long*)alloca (n * sizeof *p);
+      sink (p);
+      p = p + 1;
+    }
+    sink (p);
+  }
+}
+
+
+#pragma GCC diagnostic push
+/* Verify that -Wdangling-pointer works with #pragma diagnostic.  */
+#pragma GCC diagnostic ignored "-Wdangling-pointer"
+
+void nowarn_scalar_call_ignored (void *vp)
+{
+  int *p;
+  {
+    int i;
+    p = &i;
+  }
+  sink (p);
+}
+
+#pragma GCC diagnostic pop
+
+
+void* nowarn_return_local_addr (void)
+{
+  int a[] = { 1, 2, 3 };
+  int *p = a;
+
+  /* This is a likely bug but it's not really one of using a dangling
+     pointer but rather of returning the address of a local variable
+     which is diagnosed by -Wreturn-local-addr.  */
+  return p;
+}
+
+void* warn_return_local_addr (void)
+{
+  int *p = 0;
+  {
+    int a[] = { 1, 2, 3 };
+    p = a;
+  }
+
+  /* Unlike the above case, here the pointer is dangling when it's
+     used.  */
+  return p;                   // { dg-warning "using dangling pointer 'p' to 'a'" "array" }
+}
+
+
+void* nowarn_return_alloca (int n)
+{
+  int *p = (int*)alloca (n);
+  sink (p);
+
+  /* This is a likely bug but it's not really one of using a dangling
+     pointer but rather of returning the address of a local variable
+     which is diagnosed by -Wreturn-local-addr.  */
+  return p;
+}
+
+
+void warn_scalar_call (void)
+{
+  int *p;
+  {
+    int i;                    // { dg-message "'i' declared" "note" }
+    p = &i;
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'i'" "array" }
+}
+
+
+void warn_array_call (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" "array" }
+}
+
+
+void* warn_array_return (void)
+{
+  int *p;
+  {
+    int a[] = { 1, 2, 3 };    // { dg-message "'a' declared" "note" }
+    p = a;
+  }
+  return p;                   // { dg-warning "using dangling pointer 'p' to 'a'" "array" }
+}
+
+
+void warn_pr63272_c1 (int i)
+{
+  int *p = 0;
+
+  if (i)
+    {
+      int k = i;              // { dg-message "'k' declared" "note" }
+      p = &k;
+    }
+
+  sink (p ? *p : 0);          // { dg-warning "dangling pointer 'p' to 'k' may be used" }
+}
+
+
+void warn_pr63272_c4 (void)
+{
+  int *p = 0;
+
+  {
+    int b;                    // { dg-message "'b' declared" "note" }
+    p = &b;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'b'" "scalar" }
+}
+
+void nowarn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };
+      p = a;
+      sink (p);
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     p = b;
+     sink (p);
+   }
+
+  p = 0;
+}
+
+
+void warn_cond_if (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int *b = (int*)malloc (n);
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+}
+
+
+void warn_cond_else (int i, int n)
+{
+  int *p;
+  if (i)
+    {
+      int *a = (int*)malloc (n);
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 2, 3 };
+     sink (b);
+     p = b;
+   }
+
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
+
+
+void warn_cond_if_else (int i)
+{
+  int *p;
+  if (i)
+    {
+      int a[] = { 1, 2 };     // { dg-message "'a' declared" "note" }
+      sink (a);
+      p = a;
+    }
+  else
+   {
+     int b[] = { 3, 4 };      // { dg-message "'b' declared" "note" { xfail *-*-* } }
+     sink (b);
+     p = b;
+   }
+
+  /* With a PHI with more than invalid argument, only one use is diagnosed
+     because after the first diagnostic the code suppresses subsequent
+     ones for the same use.  This needs to be fixed.  */
+  sink (p);                   // { dg-warning "dangling pointer 'p' to 'a' may be used" }
+                              // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 }
+}
+
+
+void nowarn_gcc_i386 (int i)
+{
+  // Regression test reduced from gcc's i386.c.
+  char a[32], *p;
+
+  if (i != 1)
+    p = a;
+  else
+    p = 0;
+
+  if (i == 2)
+    sink (p);
+  else
+    {
+      if (p)
+	{
+	  sink (p);
+	  return;
+	}
+      sink (p);
+    }
+}
+
+
+void warn_memchr (char c1, char c2, char c3, char c4)
+{
+  char *p = 0;
+  {
+    char a[] = { c1, c2, c3 };// { dg-message "'a' declared" "note" }
+    p = (char*)memchr (a, c4, 3);
+    if (!p)
+      return;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" }
+}
+
+
+void warn_strchr (char c1, char c2, char c3, char c4)
+{
+  char *p = 0;
+  {
+    char a[] = { c1, c2, c3 }; // { dg-message "'a' declared" "note" }
+    p = (char*)strchr (a, c4);
+    if (!p)
+      return;
+  }
+
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to 'a'" }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
new file mode 100644
index 00000000000..54950bfb0e2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
@@ -0,0 +1,34 @@
+/* { dg-do compile }
+   { dg-options "-Wall -Wno-class-memaccess" } */
+
+extern "C" void* memset (void*, int, __SIZE_TYPE__);
+
+void sink (void*);
+
+struct S { S (); };
+
+void nowarn_array_access ()
+{
+  /* Verify that the clobber in the exceptional basic block doesn't
+     cause bogus warnings.  */
+  S a[1];
+  memset (a, 0, sizeof a);
+  sink (a);
+}
+
+
+void nowarn_array_access_cond (int i)
+{
+  if (i)
+    {
+      S a1[1];
+      memset (a1, 0, sizeof a1);
+      sink (a1);
+    }
+  else
+    {
+      S a2[2];
+      memset (a2, 0, sizeof a2);
+      sink (a2);
+    }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
index 83b6ff9157c..91a87786ae0 100644
--- a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O0 -Wall" } */
+   { dg-options "-O0 -Wall -Wno-dangling-pointer -Wno-return-local-address" } */
 
 #if __cplusplus < 201103L
 # define noexcept throw ()
@@ -18,6 +18,8 @@ extern void *p;
 void nowarn_placement_new ()
 {
   char a[sizeof (A)];
+  /* The store to the global p might trigger -Wdangling pointer or
+     -Wreturn-local-address (if/when it runs without optimization).  */
   p = new (a) A ();           // { dg-bogus "-Wfree-nonheap-object" }
 }
 
diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
new file mode 100644
index 00000000000..b5882fef69d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
@@ -0,0 +1,82 @@
+/* Exercise conditional C-only uses of dangling pointers with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memchr (const void*, int, size_t);
+extern char* strchr (const char*, int);
+
+void sink (void*, ...);
+
+
+void nowarn_compound_literal (int i, int j)
+{
+  {
+    int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 };
+    sink (p);
+  }
+  {
+    int a[] = { 1, 2, 3 };
+    int *q = i ? (int[]){ 4, 5, 6 } : a;
+    int *p = &q[1];
+    sink (p);
+  }
+  {
+    int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 };
+    int *q = __builtin_memchr (p, 2, 3 * sizeof *p);
+    sink (q);
+  }
+  {
+    int a[] = { i, i + 1, i + 2, 3 };
+    int *p = i ? (int[]){ j, j + 1, j + 2, 3 } : a;
+    int *q = __builtin_memchr (p, 3, 4 * sizeof *p);
+    sink (q);
+  }
+}
+
+
+void warn_maybe_compound_literal (int i, int j)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    p = i ? (int[]){ 4, 5, 6 } : a;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);         // { dg-warning "dangling pointer \('p' \)?to a compound literal may be used" }
+}
+
+
+void warn_maybe_compound_literal_memchr (int i, int j, int x)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int *q = i ? (int[]){ 4, 5, 6 } : a;
+    p = memchr (q, x, 3 * sizeof *q);
+  }
+  sink (p);         // { dg-warning "dangling pointer 'p' to a compound literal may be used" }
+}
+
+
+void warn_maybe_array (int i, int j)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int b[] = { 4, 5, 6 };
+    p = i ? a : b;
+  }
+  // When the 'p' is optimized away it's not mentioned in the warning.
+  sink (p);         // { dg-warning "dangling pointer \('p' \)?to 'b' may be used" }
+}
+
+
+void warn_maybe_array_memchr (int i, int j, int x)
+{
+  int a[] = { 1, 2, 3 }, *p;
+  {
+    int b[] = { 4, 5, 6 };
+    int *q = i ? a : b;
+    p = memchr (q, x, 3 * sizeof *q);
+  }
+  sink (p);         // { dg-warning "dangling pointer 'p' to 'b' may be used" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer.c b/gcc/testsuite/gcc.dg/Wdangling-pointer.c
new file mode 100644
index 00000000000..e32ac7d7a46
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wdangling-pointer.c
@@ -0,0 +1,62 @@
+/* Exercise basic C-only cases of -Wdangling-pointer.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memchr (const void*, int, size_t);
+extern char* strchr (const char*, int);
+
+void sink (const void*, ...);
+
+
+void nowarn_compound_literal (int i)
+{
+  {
+    int *p = (int[]){ 1, 2, 3 };
+    sink (p);
+  }
+  {
+    int *q = (int[]){ 1, 2, 3 };
+    int *p = &q[1];
+    sink (p);
+  }
+  {
+    int *p = __builtin_memchr ((int[]){ 1, 2, 3 }, 2, 3 * sizeof *p);
+    sink (p);
+  }
+  {
+    int *p = __builtin_memchr ((int[]){ i, i + 1 }, 3, 2 * sizeof *p);
+    sink (p);
+  }
+}
+
+
+void warn_compound_literal (int i)
+{
+  int *p;
+  {
+    p = (int[]){ 1, 2, 3 };   // { dg-message "compound literal" },
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+
+  {
+    int *q =
+      (int[]){ 1, 2, 3 };     // { dg-message "compound literal" },
+    p = &q[1];
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+  {
+    p = (int*)memchr (
+	  (int[]){ 1, 2, 3 }, // { dg-message "compound literal" }
+	  2, 3 * sizeof *p);
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+
+  {
+    p = (int*)memchr (
+	  (int[]){ i, i + 1 },// { dg-message "compound literal" }
+	  3, 2 * sizeof *p);
+  }
+  sink (p);                   // { dg-warning "using dangling pointer 'p' to a compound literal" }
+}

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

* PING [PATCH v2 1/2] add -Wuse-after-free
  2021-11-30 22:32       ` [PATCH v2 " Martin Sebor
@ 2021-12-07  0:50         ` Martin Sebor
  2021-12-13 16:48           ` PING 2 " Martin Sebor
  2022-01-11 22:40         ` Jason Merrill
  1 sibling, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-12-07  0:50 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

Ping:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html

On 11/30/21 3:32 PM, Martin Sebor wrote:
> Attached is a revised patch with the following changes based
> on your comments:
> 
> 1) Set and use statement uids to determine which statement
>     precedes which in the same basic block.
> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
> 3) Use post-dominance to decide whether to use the "maybe"
>     phrasing vs a definite form.
> 
> David raised (and in our offline discussion today reiterated)
> an objection to the default setting of the option being
> the strictest.  I have not changed that in this revision.
> See my rationale for this choice in my reply below:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
> 
> Martin
> 
> On 11/23/21 2:16 PM, Martin Sebor wrote:
>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>
>>>
>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>> made indeterminate by calls to deallocation functions like free
>>>> or C++ operator delete.  To control the conditions the warnings
>>>> are issued under the new -Wuse-after-free= option provides three
>>>> levels.  At the lowest level the warning triggers only for
>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>> in equality expressions.  Level 2 warns also for come conditional
>>>> uses, and level 3 also for uses in equality expressions.
>>>>
>>>> I debated whether to make level 2 or 3 the default included in
>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>> of both the problem and GCC's new ability to detect it: using
>>>> a pointer after it's been freed, even only in principle, by
>>>> a successful call to realloc, is undefined, and 2) because
>>>> it's trivial to lower the level either globally, or locally
>>>> by suppressing the warning around such misuses.
>>>>
>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>> due to comparing invalidated pointers for equality (i.e., level
>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>> and will see how the Glibc folks want to deal with theirs (I
>>>> track them in BZ #28521).
>>>>
>>>> The tests contain a number of xfails due to limitations I'm
>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>> I will open bugs for them before committing if I don't resolve
>>>> them in a followup.
>>>>
>>>> Martin
>>>>
>>>> gcc-63272-1.diff
>>>>
>>>> Add -Wuse-after-free.
>>>>
>>>> gcc/c-family/ChangeLog
>>>>
>>>>     * c.opt (-Wuse-after-free): New options.
>>>>
>>>> gcc/ChangeLog:
>>>>
>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>>     (pass_waccess::check_call_access): ...to this.
>>>>     (pass_waccess::check): Rename...
>>>>     (pass_waccess::check_block): ...to this.
>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>     (gimple_use_after_inval_p): New function.
>>>>     (get_realloc_lhs): New function.
>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>     (pointers_related_p): New function.
>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>
>>>> libcpp/ChangeLog:
>>>>
>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>
>>>> libiberty/ChangeLog:
>>>>
>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>
>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>> b/gcc/gimple-ssa-warn-access.cc
>>>> index 63fc27a1487..2065402a2b9 100644
>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>
>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>> (gcall *call)
>>>>       }
>>>>   }
>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>> pointer's use)
>>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>>> statement,
>>>> +   which is either a clobber or a deallocation call), or if they're in
>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>> +
>>>> +static bool
>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>> +              bool last_block = false)
>>>> +{
>>>> +  tree clobvar =
>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) 
>>>> : NULL_TREE;
>>>> +
>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>> +
>>>> +  if (inval_bb != use_bb)
>>>> +    {
>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>> +    return true;
>>>> +
>>>> +      if (!clobvar || !last_block)
>>>> +    return false;
>>>> +
>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>> +
>>>> +      auto_bitmap visited;
>>>> +
>>>> +      /* A use statement in the last basic block in a function or 
>>>> one that
>>>> +     falls through to it is after any other prior clobber of the used
>>>> +     variable unless it's followed by a clobber of the same 
>>>> variable. */
>>>> +      basic_block bb = use_bb;
>>>> +      while (bb != inval_bb
>>>> +         && single_succ_p (bb)
>>>> +         && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
>>>> +    {
>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>> +        /* Avoid cycles. */
>>>> +        return true;
>>>> +
>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>> +        {
>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>> +          if (gimple_clobber_p (stmt))
>>>> +        {
>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>> +            /* The use is followed by a clobber.  */
>>>> +            return false;
>>>> +        }
>>>> +        }
>>>> +
>>>> +      bb = single_succ (bb);
>>>> +      gsi = gsi_start_bb (bb);
>>>> +    }
>>>> +
>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>> +    }
>>> ?!?  I would have thought the block dominance test plus checking UIDs 
>>> if the two statements are in the same block would be all you need. 
>>> Can you elaborate more on what that hunk above is trying to do?
>>
>> The loop is entered only for -Wdangling-pointer.  It looks for
>> the first clobber of the CLOBVAR variable (one whose clobber
>> statement has been seen during the CFG traversal and whose use
>> is being validated) in the successors along the single edge
>> from the use block.  If the search finds a clobber, the use
>> is valid.  If it doesn't, the use is one of a variable having
>> gone out of scope (the clobber must be before the use).
>>
>> Among the cases the loop handles is the one in PR 63272
>> (the request for -Wdangling-pointer) where the use neither
>> follows the clobber in the same block nor dominated by it.
>>
>> There may be a way to optimize it somehow but because it's
>> a search I don't think a simple UID check alone would be
>> enough.
>>
>>>> +
>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>> +       gsi_next_nondebug (&si))
>>>> +    {
>>>> +      gimple *stmt = gsi_stmt (si);
>>>> +      if (stmt == use_stmt)
>>>> +    return true;
>>>> +    }
>>>> +
>>>> +  return false;
>>>> +}
>>> So from a compile-time standpoint, would it be better to to assign 
>>> UIDs to each statement so that within a block you can just compare 
>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>> statement domination within a block if we're going to be doing 
>>> multiple queries.
>>
>> I'd considered it but because statement UIDs don't exist at
>> the start of a pass, assigning them means either traversing all
>> statements in the whole CFG first, even in functions with no
>> deallocation calls or clobbers, or doing it lazily, after
>> the first such statement has been seen.  It might ultimately
>> be worthwhile if more warnings(*) end up relying on it but at
>> this point I'm not sure the optimization wouldn't end up slowing
>> things down on average.
>>
>> For some data, in a GCC bootstrap, each statement visited by
>> this loop is visited on average twice (2.2 times), and
>> the average sequence of statements traversed by the loop is
>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>> So still not sure it would be a win.
>>
>> Let me know if this is something you think I need to pursue at
>> this stage.
>>
>> [*] I think simple memory/resource leak detection might perhaps
>> be one.
>>
>>>> +
>>>> +/* Return true if P and Q point to the same object, and false if they
>>>> +   either don't or their relationship cannot be determined.  */
>>>> +
>>>> +static bool
>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>>>> +{
>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>> +    return false;
>>> Hmm, I guess that you don't need to worry about the case where P and 
>>> Q point to different elements within an array.  They point to 
>>> different final objects, though they do share a common enclosing 
>>> object. Similarly for P & Q pointing to different members within a 
>>> structure.
>>
>> Right.  The if statement is an optimization to avoid having to
>> determine the identity of the complete objects that P and Q
>> point to.  That's done by the calls to get_ref() below (for
>> complete objects; as you note, we don't care about subobjects
>> for this).
>>
>>>> +
>>>> +/* For a STMT either a call to a deallocation function or a 
>>>> clobber, warn
>>>> +   for uses of the pointer PTR it was called with (including its 
>>>> copies
>>>> +   or others derived from it by pointer arithmetic).  */
>>>> +
>>>> +void
>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>> +{
>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>> +
>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>> +
>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>> +     a single basic block issue a "maybe" k
>>> That seems wrong.   What you're looking for is a post-dominance 
>>> relationship I think.   If the sink (free/delete) is post-dominated 
>>> by the use, then it's a "must", if it's not post-dominated, then it's 
>>> a maybe.  Of course, that means you need to build post-dominators.
>>
>> I'm sure you're right in general.  To avoid false positives
>> the warning is very simplistic and only considers straight
>> paths through the CFG, so I'm not sure this matters.  But
>> I'm fine with using the post-dominance test instead if you
>> thin it's worthwhile (it doesn't change any tests).
>>
>>>> +
>>>> +      if (check_dangling
>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs only
>>>> +           with optimization enabled).  */
>>>> +        continue;
>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>> removal of erroneous paths should gate any of this code.  ISTM that 
>>> you're likely papering over a problem elsewhere.
>>
>> This code avoids issuing -Wdangling-pointer for problems that
>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>> case from Wreturn-local-addr-2.c:
>>
>>    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" } */
>>    }
>>
>> Without the test we'd end up with
>>
>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>
>> in addition to -Wreturn-local-addr (and a whole slew of
>> failures in the -Wreturn-local-addr tests).
>>
>> -Wreturn-local-addr only runs when
>> flag_isolate_erroneous_paths_dereference is nonzero, so
>> the conditional makes sure -Wdangling-pointer is issued when
>> either the flag or -Wreturn-local-addr is disabled.  I think
>> that works as expected (i.e., there's no problem elsewhere).
>>
>> I could have the code issue -Wdangling-pointer and suppress
>> -Wreturn-local-addr but that doesn't seem right since
>> the pointer hasn't gone out of scope yet at the point it's
>> returned.
>>
>> Alternatively, I could change this instance of
>> -Wdangling-pointer to -Wreturn-local-addr but that also
>> doesn't seem like good design since we have a whole pass
>> dedicated to the latter warning.
>>
>> I can't think of any other more elegant solutions but I'm open
>> to suggestions.
>>
>> Martin
> 


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

* PING [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2021-11-30 22:55   ` [PATCH v2 " Martin Sebor
@ 2021-12-07  0:51     ` Martin Sebor
  2021-12-13 16:50       ` PING 2 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-12-07  0:51 UTC (permalink / raw)
  To: gcc-patches

Ping:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html

On 11/30/21 3:55 PM, Martin Sebor wrote:
> Attached is a revision of this patch with adjustments for
> the changes to the prerequisite patch 1 in the series and
> a couple of minor simplifications and slightly improved
> test coverage, rested on x86_64-linux.
> 
> On 11/1/21 4:18 PM, Martin Sebor wrote:
>> Patch 2 in this series adds support for detecting the uses of
>> dangling pointers: those to auto objects that have gone out of
>> scope.  Like patch 1, to minimize false positives this detection
>> is very simplistic.  However, thanks to the more deterministic
>> nature of the problem (all local objects go out of scope) is able
>> to detect more instances of it.  The approach I used is to simply
>> search the IL for clobbers that dominate uses of pointers to
>> the clobbered objects.  If such a use is found that's not
>> followed by a clobber of the same object the warning triggers.
>> Similar to -Wuse-after-free, the new -Wdangling-pointer option
>> has multiple levels: level 1 to detect unconditional uses and
>> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
>> there is no use case for testing dangling pointers for
>> equality, so there is no level 3.
>>
>> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
>> It found no problems outside of the GCC test suite.
>>
>> As with the first patch in this series, the tests contain a number
>> of xfails due to known limitations marked with pr??????.  I'll
>> open bugs for them before committing the patch if I don't resolve
>> them first in a followup.
>>
>> Martin
> 


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

* PING 2 [PATCH v2 1/2] add -Wuse-after-free
  2021-12-07  0:50         ` PING " Martin Sebor
@ 2021-12-13 16:48           ` Martin Sebor
  2022-01-04 18:01             ` PING 3 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-12-13 16:48 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

Ping.

Jeff, I addressed your comments in the updated patch.  If there
are no other changes is the last revision okay to commit?

https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html

On 12/6/21 5:50 PM, Martin Sebor wrote:
> Ping:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
> 
> On 11/30/21 3:32 PM, Martin Sebor wrote:
>> Attached is a revised patch with the following changes based
>> on your comments:
>>
>> 1) Set and use statement uids to determine which statement
>>     precedes which in the same basic block.
>> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
>> 3) Use post-dominance to decide whether to use the "maybe"
>>     phrasing vs a definite form.
>>
>> David raised (and in our offline discussion today reiterated)
>> an objection to the default setting of the option being
>> the strictest.  I have not changed that in this revision.
>> See my rationale for this choice in my reply below:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
>>
>> Martin
>>
>> On 11/23/21 2:16 PM, Martin Sebor wrote:
>>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>>
>>>>
>>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>>> made indeterminate by calls to deallocation functions like free
>>>>> or C++ operator delete.  To control the conditions the warnings
>>>>> are issued under the new -Wuse-after-free= option provides three
>>>>> levels.  At the lowest level the warning triggers only for
>>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>>> in equality expressions.  Level 2 warns also for come conditional
>>>>> uses, and level 3 also for uses in equality expressions.
>>>>>
>>>>> I debated whether to make level 2 or 3 the default included in
>>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>>> of both the problem and GCC's new ability to detect it: using
>>>>> a pointer after it's been freed, even only in principle, by
>>>>> a successful call to realloc, is undefined, and 2) because
>>>>> it's trivial to lower the level either globally, or locally
>>>>> by suppressing the warning around such misuses.
>>>>>
>>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>>> due to comparing invalidated pointers for equality (i.e., level
>>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>>> and will see how the Glibc folks want to deal with theirs (I
>>>>> track them in BZ #28521).
>>>>>
>>>>> The tests contain a number of xfails due to limitations I'm
>>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>>> I will open bugs for them before committing if I don't resolve
>>>>> them in a followup.
>>>>>
>>>>> Martin
>>>>>
>>>>> gcc-63272-1.diff
>>>>>
>>>>> Add -Wuse-after-free.
>>>>>
>>>>> gcc/c-family/ChangeLog
>>>>>
>>>>>     * c.opt (-Wuse-after-free): New options.
>>>>>
>>>>> gcc/ChangeLog:
>>>>>
>>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>>>     (pass_waccess::check_call_access): ...to this.
>>>>>     (pass_waccess::check): Rename...
>>>>>     (pass_waccess::check_block): ...to this.
>>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>>     (gimple_use_after_inval_p): New function.
>>>>>     (get_realloc_lhs): New function.
>>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>>     (pointers_related_p): New function.
>>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>>
>>>>> libcpp/ChangeLog:
>>>>>
>>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>>
>>>>> libiberty/ChangeLog:
>>>>>
>>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>>
>>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>>> b/gcc/gimple-ssa-warn-access.cc
>>>>> index 63fc27a1487..2065402a2b9 100644
>>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>>
>>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>>> (gcall *call)
>>>>>       }
>>>>>   }
>>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>>> pointer's use)
>>>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>>>> statement,
>>>>> +   which is either a clobber or a deallocation call), or if 
>>>>> they're in
>>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>>> +
>>>>> +static bool
>>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>>> +              bool last_block = false)
>>>>> +{
>>>>> +  tree clobvar =
>>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) 
>>>>> : NULL_TREE;
>>>>> +
>>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>>> +
>>>>> +  if (inval_bb != use_bb)
>>>>> +    {
>>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>>> +    return true;
>>>>> +
>>>>> +      if (!clobvar || !last_block)
>>>>> +    return false;
>>>>> +
>>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>>> +
>>>>> +      auto_bitmap visited;
>>>>> +
>>>>> +      /* A use statement in the last basic block in a function or 
>>>>> one that
>>>>> +     falls through to it is after any other prior clobber of the used
>>>>> +     variable unless it's followed by a clobber of the same 
>>>>> variable. */
>>>>> +      basic_block bb = use_bb;
>>>>> +      while (bb != inval_bb
>>>>> +         && single_succ_p (bb)
>>>>> +         && !(single_succ_edge (bb)->flags & 
>>>>> (EDGE_EH|EDGE_DFS_BACK)))
>>>>> +    {
>>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>>> +        /* Avoid cycles. */
>>>>> +        return true;
>>>>> +
>>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>>> +        {
>>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>>> +          if (gimple_clobber_p (stmt))
>>>>> +        {
>>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>>> +            /* The use is followed by a clobber.  */
>>>>> +            return false;
>>>>> +        }
>>>>> +        }
>>>>> +
>>>>> +      bb = single_succ (bb);
>>>>> +      gsi = gsi_start_bb (bb);
>>>>> +    }
>>>>> +
>>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>>> +    }
>>>> ?!?  I would have thought the block dominance test plus checking 
>>>> UIDs if the two statements are in the same block would be all you 
>>>> need. Can you elaborate more on what that hunk above is trying to do?
>>>
>>> The loop is entered only for -Wdangling-pointer.  It looks for
>>> the first clobber of the CLOBVAR variable (one whose clobber
>>> statement has been seen during the CFG traversal and whose use
>>> is being validated) in the successors along the single edge
>>> from the use block.  If the search finds a clobber, the use
>>> is valid.  If it doesn't, the use is one of a variable having
>>> gone out of scope (the clobber must be before the use).
>>>
>>> Among the cases the loop handles is the one in PR 63272
>>> (the request for -Wdangling-pointer) where the use neither
>>> follows the clobber in the same block nor dominated by it.
>>>
>>> There may be a way to optimize it somehow but because it's
>>> a search I don't think a simple UID check alone would be
>>> enough.
>>>
>>>>> +
>>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>>> +       gsi_next_nondebug (&si))
>>>>> +    {
>>>>> +      gimple *stmt = gsi_stmt (si);
>>>>> +      if (stmt == use_stmt)
>>>>> +    return true;
>>>>> +    }
>>>>> +
>>>>> +  return false;
>>>>> +}
>>>> So from a compile-time standpoint, would it be better to to assign 
>>>> UIDs to each statement so that within a block you can just compare 
>>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>>> statement domination within a block if we're going to be doing 
>>>> multiple queries.
>>>
>>> I'd considered it but because statement UIDs don't exist at
>>> the start of a pass, assigning them means either traversing all
>>> statements in the whole CFG first, even in functions with no
>>> deallocation calls or clobbers, or doing it lazily, after
>>> the first such statement has been seen.  It might ultimately
>>> be worthwhile if more warnings(*) end up relying on it but at
>>> this point I'm not sure the optimization wouldn't end up slowing
>>> things down on average.
>>>
>>> For some data, in a GCC bootstrap, each statement visited by
>>> this loop is visited on average twice (2.2 times), and
>>> the average sequence of statements traversed by the loop is
>>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>>> So still not sure it would be a win.
>>>
>>> Let me know if this is something you think I need to pursue at
>>> this stage.
>>>
>>> [*] I think simple memory/resource leak detection might perhaps
>>> be one.
>>>
>>>>> +
>>>>> +/* Return true if P and Q point to the same object, and false if they
>>>>> +   either don't or their relationship cannot be determined.  */
>>>>> +
>>>>> +static bool
>>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>>>>> +{
>>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>>> +    return false;
>>>> Hmm, I guess that you don't need to worry about the case where P and 
>>>> Q point to different elements within an array.  They point to 
>>>> different final objects, though they do share a common enclosing 
>>>> object. Similarly for P & Q pointing to different members within a 
>>>> structure.
>>>
>>> Right.  The if statement is an optimization to avoid having to
>>> determine the identity of the complete objects that P and Q
>>> point to.  That's done by the calls to get_ref() below (for
>>> complete objects; as you note, we don't care about subobjects
>>> for this).
>>>
>>>>> +
>>>>> +/* For a STMT either a call to a deallocation function or a 
>>>>> clobber, warn
>>>>> +   for uses of the pointer PTR it was called with (including its 
>>>>> copies
>>>>> +   or others derived from it by pointer arithmetic).  */
>>>>> +
>>>>> +void
>>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>>> +{
>>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>>> +
>>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>>> +
>>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>>> +     a single basic block issue a "maybe" k
>>>> That seems wrong.   What you're looking for is a post-dominance 
>>>> relationship I think.   If the sink (free/delete) is post-dominated 
>>>> by the use, then it's a "must", if it's not post-dominated, then 
>>>> it's a maybe.  Of course, that means you need to build post-dominators.
>>>
>>> I'm sure you're right in general.  To avoid false positives
>>> the warning is very simplistic and only considers straight
>>> paths through the CFG, so I'm not sure this matters.  But
>>> I'm fine with using the post-dominance test instead if you
>>> thin it's worthwhile (it doesn't change any tests).
>>>
>>>>> +
>>>>> +      if (check_dangling
>>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs 
>>>>> only
>>>>> +           with optimization enabled).  */
>>>>> +        continue;
>>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>>> removal of erroneous paths should gate any of this code.  ISTM that 
>>>> you're likely papering over a problem elsewhere.
>>>
>>> This code avoids issuing -Wdangling-pointer for problems that
>>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>>> case from Wreturn-local-addr-2.c:
>>>
>>>    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" } */
>>>    }
>>>
>>> Without the test we'd end up with
>>>
>>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>>
>>> in addition to -Wreturn-local-addr (and a whole slew of
>>> failures in the -Wreturn-local-addr tests).
>>>
>>> -Wreturn-local-addr only runs when
>>> flag_isolate_erroneous_paths_dereference is nonzero, so
>>> the conditional makes sure -Wdangling-pointer is issued when
>>> either the flag or -Wreturn-local-addr is disabled.  I think
>>> that works as expected (i.e., there's no problem elsewhere).
>>>
>>> I could have the code issue -Wdangling-pointer and suppress
>>> -Wreturn-local-addr but that doesn't seem right since
>>> the pointer hasn't gone out of scope yet at the point it's
>>> returned.
>>>
>>> Alternatively, I could change this instance of
>>> -Wdangling-pointer to -Wreturn-local-addr but that also
>>> doesn't seem like good design since we have a whole pass
>>> dedicated to the latter warning.
>>>
>>> I can't think of any other more elegant solutions but I'm open
>>> to suggestions.
>>>
>>> Martin
>>
> 


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

* PING 2 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2021-12-07  0:51     ` PING " Martin Sebor
@ 2021-12-13 16:50       ` Martin Sebor
  2022-01-04 18:02         ` PING 3 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2021-12-13 16:50 UTC (permalink / raw)
  To: gcc-patches

Ping.  This patch, originally submitted on Nov. 1, has not been
reviewed yet.

https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html

On 12/6/21 5:51 PM, Martin Sebor wrote:
> Ping:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
> 
> On 11/30/21 3:55 PM, Martin Sebor wrote:
>> Attached is a revision of this patch with adjustments for
>> the changes to the prerequisite patch 1 in the series and
>> a couple of minor simplifications and slightly improved
>> test coverage, rested on x86_64-linux.
>>
>> On 11/1/21 4:18 PM, Martin Sebor wrote:
>>> Patch 2 in this series adds support for detecting the uses of
>>> dangling pointers: those to auto objects that have gone out of
>>> scope.  Like patch 1, to minimize false positives this detection
>>> is very simplistic.  However, thanks to the more deterministic
>>> nature of the problem (all local objects go out of scope) is able
>>> to detect more instances of it.  The approach I used is to simply
>>> search the IL for clobbers that dominate uses of pointers to
>>> the clobbered objects.  If such a use is found that's not
>>> followed by a clobber of the same object the warning triggers.
>>> Similar to -Wuse-after-free, the new -Wdangling-pointer option
>>> has multiple levels: level 1 to detect unconditional uses and
>>> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
>>> there is no use case for testing dangling pointers for
>>> equality, so there is no level 3.
>>>
>>> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
>>> It found no problems outside of the GCC test suite.
>>>
>>> As with the first patch in this series, the tests contain a number
>>> of xfails due to known limitations marked with pr??????.  I'll
>>> open bugs for them before committing the patch if I don't resolve
>>> them first in a followup.
>>>
>>> Martin
>>
> 


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

* PING 3 [PATCH v2 1/2] add -Wuse-after-free
  2021-12-13 16:48           ` PING 2 " Martin Sebor
@ 2022-01-04 18:01             ` Martin Sebor
  2022-01-10 21:58               ` PING 4 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2022-01-04 18:01 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

Ping.  (CC'ing Jason as requested.)

https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html

On 12/13/21 9:48 AM, Martin Sebor wrote:
> Ping.
> 
> Jeff, I addressed your comments in the updated patch.  If there
> are no other changes is the last revision okay to commit?
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
> 
> On 12/6/21 5:50 PM, Martin Sebor wrote:
>> Ping:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
>>
>> On 11/30/21 3:32 PM, Martin Sebor wrote:
>>> Attached is a revised patch with the following changes based
>>> on your comments:
>>>
>>> 1) Set and use statement uids to determine which statement
>>>     precedes which in the same basic block.
>>> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
>>> 3) Use post-dominance to decide whether to use the "maybe"
>>>     phrasing vs a definite form.
>>>
>>> David raised (and in our offline discussion today reiterated)
>>> an objection to the default setting of the option being
>>> the strictest.  I have not changed that in this revision.
>>> See my rationale for this choice in my reply below:
>>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
>>>
>>> Martin
>>>
>>> On 11/23/21 2:16 PM, Martin Sebor wrote:
>>>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>>>
>>>>>
>>>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>>>> made indeterminate by calls to deallocation functions like free
>>>>>> or C++ operator delete.  To control the conditions the warnings
>>>>>> are issued under the new -Wuse-after-free= option provides three
>>>>>> levels.  At the lowest level the warning triggers only for
>>>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>>>> in equality expressions.  Level 2 warns also for come conditional
>>>>>> uses, and level 3 also for uses in equality expressions.
>>>>>>
>>>>>> I debated whether to make level 2 or 3 the default included in
>>>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>>>> of both the problem and GCC's new ability to detect it: using
>>>>>> a pointer after it's been freed, even only in principle, by
>>>>>> a successful call to realloc, is undefined, and 2) because
>>>>>> it's trivial to lower the level either globally, or locally
>>>>>> by suppressing the warning around such misuses.
>>>>>>
>>>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>>>> due to comparing invalidated pointers for equality (i.e., level
>>>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>>>> and will see how the Glibc folks want to deal with theirs (I
>>>>>> track them in BZ #28521).
>>>>>>
>>>>>> The tests contain a number of xfails due to limitations I'm
>>>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>>>> I will open bugs for them before committing if I don't resolve
>>>>>> them in a followup.
>>>>>>
>>>>>> Martin
>>>>>>
>>>>>> gcc-63272-1.diff
>>>>>>
>>>>>> Add -Wuse-after-free.
>>>>>>
>>>>>> gcc/c-family/ChangeLog
>>>>>>
>>>>>>     * c.opt (-Wuse-after-free): New options.
>>>>>>
>>>>>> gcc/ChangeLog:
>>>>>>
>>>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>>>>     (pass_waccess::check_call_access): ...to this.
>>>>>>     (pass_waccess::check): Rename...
>>>>>>     (pass_waccess::check_block): ...to this.
>>>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>>>     (gimple_use_after_inval_p): New function.
>>>>>>     (get_realloc_lhs): New function.
>>>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>>>     (pointers_related_p): New function.
>>>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>>>
>>>>>> libcpp/ChangeLog:
>>>>>>
>>>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>>>
>>>>>> libiberty/ChangeLog:
>>>>>>
>>>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>>>
>>>>>> gcc/testsuite/ChangeLog:
>>>>>>
>>>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>>>
>>>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>>>> b/gcc/gimple-ssa-warn-access.cc
>>>>>> index 63fc27a1487..2065402a2b9 100644
>>>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>>>
>>>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>>>> (gcall *call)
>>>>>>       }
>>>>>>   }
>>>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>>>> pointer's use)
>>>>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>>>>> statement,
>>>>>> +   which is either a clobber or a deallocation call), or if 
>>>>>> they're in
>>>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>>>> +
>>>>>> +static bool
>>>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>>>> +              bool last_block = false)
>>>>>> +{
>>>>>> +  tree clobvar =
>>>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs 
>>>>>> (inval_stmt) : NULL_TREE;
>>>>>> +
>>>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>>>> +
>>>>>> +  if (inval_bb != use_bb)
>>>>>> +    {
>>>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>>>> +    return true;
>>>>>> +
>>>>>> +      if (!clobvar || !last_block)
>>>>>> +    return false;
>>>>>> +
>>>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>>>> +
>>>>>> +      auto_bitmap visited;
>>>>>> +
>>>>>> +      /* A use statement in the last basic block in a function or 
>>>>>> one that
>>>>>> +     falls through to it is after any other prior clobber of the 
>>>>>> used
>>>>>> +     variable unless it's followed by a clobber of the same 
>>>>>> variable. */
>>>>>> +      basic_block bb = use_bb;
>>>>>> +      while (bb != inval_bb
>>>>>> +         && single_succ_p (bb)
>>>>>> +         && !(single_succ_edge (bb)->flags & 
>>>>>> (EDGE_EH|EDGE_DFS_BACK)))
>>>>>> +    {
>>>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>>>> +        /* Avoid cycles. */
>>>>>> +        return true;
>>>>>> +
>>>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>>>> +        {
>>>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>>>> +          if (gimple_clobber_p (stmt))
>>>>>> +        {
>>>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>>>> +            /* The use is followed by a clobber.  */
>>>>>> +            return false;
>>>>>> +        }
>>>>>> +        }
>>>>>> +
>>>>>> +      bb = single_succ (bb);
>>>>>> +      gsi = gsi_start_bb (bb);
>>>>>> +    }
>>>>>> +
>>>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>>>> +    }
>>>>> ?!?  I would have thought the block dominance test plus checking 
>>>>> UIDs if the two statements are in the same block would be all you 
>>>>> need. Can you elaborate more on what that hunk above is trying to do?
>>>>
>>>> The loop is entered only for -Wdangling-pointer.  It looks for
>>>> the first clobber of the CLOBVAR variable (one whose clobber
>>>> statement has been seen during the CFG traversal and whose use
>>>> is being validated) in the successors along the single edge
>>>> from the use block.  If the search finds a clobber, the use
>>>> is valid.  If it doesn't, the use is one of a variable having
>>>> gone out of scope (the clobber must be before the use).
>>>>
>>>> Among the cases the loop handles is the one in PR 63272
>>>> (the request for -Wdangling-pointer) where the use neither
>>>> follows the clobber in the same block nor dominated by it.
>>>>
>>>> There may be a way to optimize it somehow but because it's
>>>> a search I don't think a simple UID check alone would be
>>>> enough.
>>>>
>>>>>> +
>>>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>>>> +       gsi_next_nondebug (&si))
>>>>>> +    {
>>>>>> +      gimple *stmt = gsi_stmt (si);
>>>>>> +      if (stmt == use_stmt)
>>>>>> +    return true;
>>>>>> +    }
>>>>>> +
>>>>>> +  return false;
>>>>>> +}
>>>>> So from a compile-time standpoint, would it be better to to assign 
>>>>> UIDs to each statement so that within a block you can just compare 
>>>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>>>> statement domination within a block if we're going to be doing 
>>>>> multiple queries.
>>>>
>>>> I'd considered it but because statement UIDs don't exist at
>>>> the start of a pass, assigning them means either traversing all
>>>> statements in the whole CFG first, even in functions with no
>>>> deallocation calls or clobbers, or doing it lazily, after
>>>> the first such statement has been seen.  It might ultimately
>>>> be worthwhile if more warnings(*) end up relying on it but at
>>>> this point I'm not sure the optimization wouldn't end up slowing
>>>> things down on average.
>>>>
>>>> For some data, in a GCC bootstrap, each statement visited by
>>>> this loop is visited on average twice (2.2 times), and
>>>> the average sequence of statements traversed by the loop is
>>>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>>>> So still not sure it would be a win.
>>>>
>>>> Let me know if this is something you think I need to pursue at
>>>> this stage.
>>>>
>>>> [*] I think simple memory/resource leak detection might perhaps
>>>> be one.
>>>>
>>>>>> +
>>>>>> +/* Return true if P and Q point to the same object, and false if 
>>>>>> they
>>>>>> +   either don't or their relationship cannot be determined.  */
>>>>>> +
>>>>>> +static bool
>>>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query 
>>>>>> &qry)
>>>>>> +{
>>>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>>>> +    return false;
>>>>> Hmm, I guess that you don't need to worry about the case where P 
>>>>> and Q point to different elements within an array.  They point to 
>>>>> different final objects, though they do share a common enclosing 
>>>>> object. Similarly for P & Q pointing to different members within a 
>>>>> structure.
>>>>
>>>> Right.  The if statement is an optimization to avoid having to
>>>> determine the identity of the complete objects that P and Q
>>>> point to.  That's done by the calls to get_ref() below (for
>>>> complete objects; as you note, we don't care about subobjects
>>>> for this).
>>>>
>>>>>> +
>>>>>> +/* For a STMT either a call to a deallocation function or a 
>>>>>> clobber, warn
>>>>>> +   for uses of the pointer PTR it was called with (including its 
>>>>>> copies
>>>>>> +   or others derived from it by pointer arithmetic).  */
>>>>>> +
>>>>>> +void
>>>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>>>> +{
>>>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>>>> +
>>>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>>>> +
>>>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>>>> +     a single basic block issue a "maybe" k
>>>>> That seems wrong.   What you're looking for is a post-dominance 
>>>>> relationship I think.   If the sink (free/delete) is post-dominated 
>>>>> by the use, then it's a "must", if it's not post-dominated, then 
>>>>> it's a maybe.  Of course, that means you need to build 
>>>>> post-dominators.
>>>>
>>>> I'm sure you're right in general.  To avoid false positives
>>>> the warning is very simplistic and only considers straight
>>>> paths through the CFG, so I'm not sure this matters.  But
>>>> I'm fine with using the post-dominance test instead if you
>>>> thin it's worthwhile (it doesn't change any tests).
>>>>
>>>>>> +
>>>>>> +      if (check_dangling
>>>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs 
>>>>>> only
>>>>>> +           with optimization enabled).  */
>>>>>> +        continue;
>>>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>>>> removal of erroneous paths should gate any of this code.  ISTM that 
>>>>> you're likely papering over a problem elsewhere.
>>>>
>>>> This code avoids issuing -Wdangling-pointer for problems that
>>>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>>>> case from Wreturn-local-addr-2.c:
>>>>
>>>>    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" } */
>>>>    }
>>>>
>>>> Without the test we'd end up with
>>>>
>>>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>>>
>>>> in addition to -Wreturn-local-addr (and a whole slew of
>>>> failures in the -Wreturn-local-addr tests).
>>>>
>>>> -Wreturn-local-addr only runs when
>>>> flag_isolate_erroneous_paths_dereference is nonzero, so
>>>> the conditional makes sure -Wdangling-pointer is issued when
>>>> either the flag or -Wreturn-local-addr is disabled.  I think
>>>> that works as expected (i.e., there's no problem elsewhere).
>>>>
>>>> I could have the code issue -Wdangling-pointer and suppress
>>>> -Wreturn-local-addr but that doesn't seem right since
>>>> the pointer hasn't gone out of scope yet at the point it's
>>>> returned.
>>>>
>>>> Alternatively, I could change this instance of
>>>> -Wdangling-pointer to -Wreturn-local-addr but that also
>>>> doesn't seem like good design since we have a whole pass
>>>> dedicated to the latter warning.
>>>>
>>>> I can't think of any other more elegant solutions but I'm open
>>>> to suggestions.
>>>>
>>>> Martin
>>>
>>
> 


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

* PING 3 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2021-12-13 16:50       ` PING 2 " Martin Sebor
@ 2022-01-04 18:02         ` Martin Sebor
  2022-01-10 21:51           ` PING 4 " Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2022-01-04 18:02 UTC (permalink / raw)
  To: gcc-patches

Ping:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html

On 12/13/21 9:50 AM, Martin Sebor wrote:
> Ping.  This patch, originally submitted on Nov. 1, has not been
> reviewed yet.
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
> 
> On 12/6/21 5:51 PM, Martin Sebor wrote:
>> Ping:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
>>
>> On 11/30/21 3:55 PM, Martin Sebor wrote:
>>> Attached is a revision of this patch with adjustments for
>>> the changes to the prerequisite patch 1 in the series and
>>> a couple of minor simplifications and slightly improved
>>> test coverage, rested on x86_64-linux.
>>>
>>> On 11/1/21 4:18 PM, Martin Sebor wrote:
>>>> Patch 2 in this series adds support for detecting the uses of
>>>> dangling pointers: those to auto objects that have gone out of
>>>> scope.  Like patch 1, to minimize false positives this detection
>>>> is very simplistic.  However, thanks to the more deterministic
>>>> nature of the problem (all local objects go out of scope) is able
>>>> to detect more instances of it.  The approach I used is to simply
>>>> search the IL for clobbers that dominate uses of pointers to
>>>> the clobbered objects.  If such a use is found that's not
>>>> followed by a clobber of the same object the warning triggers.
>>>> Similar to -Wuse-after-free, the new -Wdangling-pointer option
>>>> has multiple levels: level 1 to detect unconditional uses and
>>>> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
>>>> there is no use case for testing dangling pointers for
>>>> equality, so there is no level 3.
>>>>
>>>> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
>>>> It found no problems outside of the GCC test suite.
>>>>
>>>> As with the first patch in this series, the tests contain a number
>>>> of xfails due to known limitations marked with pr??????.  I'll
>>>> open bugs for them before committing the patch if I don't resolve
>>>> them first in a followup.
>>>>
>>>> Martin
>>>
>>
> 


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

* PING 4 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2022-01-04 18:02         ` PING 3 " Martin Sebor
@ 2022-01-10 21:51           ` Martin Sebor
  2022-01-17 13:46             ` Stephan Bergmann
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2022-01-10 21:51 UTC (permalink / raw)
  To: gcc-patches

Last ping for this stage 1 feature before stage 3 ends:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html

On 1/4/22 11:02, Martin Sebor wrote:
> Ping:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
> 
> On 12/13/21 9:50 AM, Martin Sebor wrote:
>> Ping.  This patch, originally submitted on Nov. 1, has not been
>> reviewed yet.
>>
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
>>
>> On 12/6/21 5:51 PM, Martin Sebor wrote:
>>> Ping:
>>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
>>>
>>> On 11/30/21 3:55 PM, Martin Sebor wrote:
>>>> Attached is a revision of this patch with adjustments for
>>>> the changes to the prerequisite patch 1 in the series and
>>>> a couple of minor simplifications and slightly improved
>>>> test coverage, rested on x86_64-linux.
>>>>
>>>> On 11/1/21 4:18 PM, Martin Sebor wrote:
>>>>> Patch 2 in this series adds support for detecting the uses of
>>>>> dangling pointers: those to auto objects that have gone out of
>>>>> scope.  Like patch 1, to minimize false positives this detection
>>>>> is very simplistic.  However, thanks to the more deterministic
>>>>> nature of the problem (all local objects go out of scope) is able
>>>>> to detect more instances of it.  The approach I used is to simply
>>>>> search the IL for clobbers that dominate uses of pointers to
>>>>> the clobbered objects.  If such a use is found that's not
>>>>> followed by a clobber of the same object the warning triggers.
>>>>> Similar to -Wuse-after-free, the new -Wdangling-pointer option
>>>>> has multiple levels: level 1 to detect unconditional uses and
>>>>> level 2 to flag conditional ones.  Unlike with -Wuse-after-free
>>>>> there is no use case for testing dangling pointers for
>>>>> equality, so there is no level 3.
>>>>>
>>>>> Tested on x86_64-linux and  by building Glibc and Binutils/GDB.
>>>>> It found no problems outside of the GCC test suite.
>>>>>
>>>>> As with the first patch in this series, the tests contain a number
>>>>> of xfails due to known limitations marked with pr??????.  I'll
>>>>> open bugs for them before committing the patch if I don't resolve
>>>>> them first in a followup.
>>>>>
>>>>> Martin
>>>>
>>>
>>
> 


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

* PING 4 [PATCH v2 1/2] add -Wuse-after-free
  2022-01-04 18:01             ` PING 3 " Martin Sebor
@ 2022-01-10 21:58               ` Martin Sebor
  0 siblings, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2022-01-10 21:58 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

Last ping before stage 3 ends:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html

On 1/4/22 11:01, Martin Sebor wrote:
> Ping.  (CC'ing Jason as requested.)
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
> 
> On 12/13/21 9:48 AM, Martin Sebor wrote:
>> Ping.
>>
>> Jeff, I addressed your comments in the updated patch.  If there
>> are no other changes is the last revision okay to commit?
>>
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
>>
>> On 12/6/21 5:50 PM, Martin Sebor wrote:
>>> Ping:
>>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585816.html
>>>
>>> On 11/30/21 3:32 PM, Martin Sebor wrote:
>>>> Attached is a revised patch with the following changes based
>>>> on your comments:
>>>>
>>>> 1) Set and use statement uids to determine which statement
>>>>     precedes which in the same basic block.
>>>> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
>>>> 3) Use post-dominance to decide whether to use the "maybe"
>>>>     phrasing vs a definite form.
>>>>
>>>> David raised (and in our offline discussion today reiterated)
>>>> an objection to the default setting of the option being
>>>> the strictest.  I have not changed that in this revision.
>>>> See my rationale for this choice in my reply below:
>>>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
>>>>
>>>> Martin
>>>>
>>>> On 11/23/21 2:16 PM, Martin Sebor wrote:
>>>>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>>>>
>>>>>>
>>>>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>>>>> made indeterminate by calls to deallocation functions like free
>>>>>>> or C++ operator delete.  To control the conditions the warnings
>>>>>>> are issued under the new -Wuse-after-free= option provides three
>>>>>>> levels.  At the lowest level the warning triggers only for
>>>>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>>>>> in equality expressions.  Level 2 warns also for come conditional
>>>>>>> uses, and level 3 also for uses in equality expressions.
>>>>>>>
>>>>>>> I debated whether to make level 2 or 3 the default included in
>>>>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>>>>> of both the problem and GCC's new ability to detect it: using
>>>>>>> a pointer after it's been freed, even only in principle, by
>>>>>>> a successful call to realloc, is undefined, and 2) because
>>>>>>> it's trivial to lower the level either globally, or locally
>>>>>>> by suppressing the warning around such misuses.
>>>>>>>
>>>>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>>>>> due to comparing invalidated pointers for equality (i.e., level
>>>>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>>>>> and will see how the Glibc folks want to deal with theirs (I
>>>>>>> track them in BZ #28521).
>>>>>>>
>>>>>>> The tests contain a number of xfails due to limitations I'm
>>>>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>>>>> I will open bugs for them before committing if I don't resolve
>>>>>>> them in a followup.
>>>>>>>
>>>>>>> Martin
>>>>>>>
>>>>>>> gcc-63272-1.diff
>>>>>>>
>>>>>>> Add -Wuse-after-free.
>>>>>>>
>>>>>>> gcc/c-family/ChangeLog
>>>>>>>
>>>>>>>     * c.opt (-Wuse-after-free): New options.
>>>>>>>
>>>>>>> gcc/ChangeLog:
>>>>>>>
>>>>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): 
>>>>>>> Rename...
>>>>>>>     (pass_waccess::check_call_access): ...to this.
>>>>>>>     (pass_waccess::check): Rename...
>>>>>>>     (pass_waccess::check_block): ...to this.
>>>>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>>>>     (gimple_use_after_inval_p): New function.
>>>>>>>     (get_realloc_lhs): New function.
>>>>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>>>>     (pointers_related_p): New function.
>>>>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>>>>
>>>>>>> libcpp/ChangeLog:
>>>>>>>
>>>>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>>>>
>>>>>>> libiberty/ChangeLog:
>>>>>>>
>>>>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>>>>
>>>>>>> gcc/testsuite/ChangeLog:
>>>>>>>
>>>>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>>>>
>>>>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>>>>> b/gcc/gimple-ssa-warn-access.cc
>>>>>>> index 63fc27a1487..2065402a2b9 100644
>>>>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>>>>
>>>>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>>>>> (gcall *call)
>>>>>>>       }
>>>>>>>   }
>>>>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>>>>> pointer's use)
>>>>>>> +   is dominated by INVAL_STMT's (that of a pointer's 
>>>>>>> invalidating statement,
>>>>>>> +   which is either a clobber or a deallocation call), or if 
>>>>>>> they're in
>>>>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>>>>> +              bool last_block = false)
>>>>>>> +{
>>>>>>> +  tree clobvar =
>>>>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs 
>>>>>>> (inval_stmt) : NULL_TREE;
>>>>>>> +
>>>>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>>>>> +
>>>>>>> +  if (inval_bb != use_bb)
>>>>>>> +    {
>>>>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>>>>> +    return true;
>>>>>>> +
>>>>>>> +      if (!clobvar || !last_block)
>>>>>>> +    return false;
>>>>>>> +
>>>>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>>>>> +
>>>>>>> +      auto_bitmap visited;
>>>>>>> +
>>>>>>> +      /* A use statement in the last basic block in a function 
>>>>>>> or one that
>>>>>>> +     falls through to it is after any other prior clobber of the 
>>>>>>> used
>>>>>>> +     variable unless it's followed by a clobber of the same 
>>>>>>> variable. */
>>>>>>> +      basic_block bb = use_bb;
>>>>>>> +      while (bb != inval_bb
>>>>>>> +         && single_succ_p (bb)
>>>>>>> +         && !(single_succ_edge (bb)->flags & 
>>>>>>> (EDGE_EH|EDGE_DFS_BACK)))
>>>>>>> +    {
>>>>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>>>>> +        /* Avoid cycles. */
>>>>>>> +        return true;
>>>>>>> +
>>>>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>>>>> +        {
>>>>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>>>>> +          if (gimple_clobber_p (stmt))
>>>>>>> +        {
>>>>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>>>>> +            /* The use is followed by a clobber.  */
>>>>>>> +            return false;
>>>>>>> +        }
>>>>>>> +        }
>>>>>>> +
>>>>>>> +      bb = single_succ (bb);
>>>>>>> +      gsi = gsi_start_bb (bb);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>>>>> +    }
>>>>>> ?!?  I would have thought the block dominance test plus checking 
>>>>>> UIDs if the two statements are in the same block would be all you 
>>>>>> need. Can you elaborate more on what that hunk above is trying to do?
>>>>>
>>>>> The loop is entered only for -Wdangling-pointer.  It looks for
>>>>> the first clobber of the CLOBVAR variable (one whose clobber
>>>>> statement has been seen during the CFG traversal and whose use
>>>>> is being validated) in the successors along the single edge
>>>>> from the use block.  If the search finds a clobber, the use
>>>>> is valid.  If it doesn't, the use is one of a variable having
>>>>> gone out of scope (the clobber must be before the use).
>>>>>
>>>>> Among the cases the loop handles is the one in PR 63272
>>>>> (the request for -Wdangling-pointer) where the use neither
>>>>> follows the clobber in the same block nor dominated by it.
>>>>>
>>>>> There may be a way to optimize it somehow but because it's
>>>>> a search I don't think a simple UID check alone would be
>>>>> enough.
>>>>>
>>>>>>> +
>>>>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>>>>> +       gsi_next_nondebug (&si))
>>>>>>> +    {
>>>>>>> +      gimple *stmt = gsi_stmt (si);
>>>>>>> +      if (stmt == use_stmt)
>>>>>>> +    return true;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +  return false;
>>>>>>> +}
>>>>>> So from a compile-time standpoint, would it be better to to assign 
>>>>>> UIDs to each statement so that within a block you can just compare 
>>>>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>>>>> statement domination within a block if we're going to be doing 
>>>>>> multiple queries.
>>>>>
>>>>> I'd considered it but because statement UIDs don't exist at
>>>>> the start of a pass, assigning them means either traversing all
>>>>> statements in the whole CFG first, even in functions with no
>>>>> deallocation calls or clobbers, or doing it lazily, after
>>>>> the first such statement has been seen.  It might ultimately
>>>>> be worthwhile if more warnings(*) end up relying on it but at
>>>>> this point I'm not sure the optimization wouldn't end up slowing
>>>>> things down on average.
>>>>>
>>>>> For some data, in a GCC bootstrap, each statement visited by
>>>>> this loop is visited on average twice (2.2 times), and
>>>>> the average sequence of statements traversed by the loop is
>>>>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>>>>> So still not sure it would be a win.
>>>>>
>>>>> Let me know if this is something you think I need to pursue at
>>>>> this stage.
>>>>>
>>>>> [*] I think simple memory/resource leak detection might perhaps
>>>>> be one.
>>>>>
>>>>>>> +
>>>>>>> +/* Return true if P and Q point to the same object, and false if 
>>>>>>> they
>>>>>>> +   either don't or their relationship cannot be determined.  */
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query 
>>>>>>> &qry)
>>>>>>> +{
>>>>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>>>>> +    return false;
>>>>>> Hmm, I guess that you don't need to worry about the case where P 
>>>>>> and Q point to different elements within an array.  They point to 
>>>>>> different final objects, though they do share a common enclosing 
>>>>>> object. Similarly for P & Q pointing to different members within a 
>>>>>> structure.
>>>>>
>>>>> Right.  The if statement is an optimization to avoid having to
>>>>> determine the identity of the complete objects that P and Q
>>>>> point to.  That's done by the calls to get_ref() below (for
>>>>> complete objects; as you note, we don't care about subobjects
>>>>> for this).
>>>>>
>>>>>>> +
>>>>>>> +/* For a STMT either a call to a deallocation function or a 
>>>>>>> clobber, warn
>>>>>>> +   for uses of the pointer PTR it was called with (including its 
>>>>>>> copies
>>>>>>> +   or others derived from it by pointer arithmetic).  */
>>>>>>> +
>>>>>>> +void
>>>>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>>>>> +{
>>>>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>>>>> +
>>>>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>>>>> +
>>>>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>>>>> +     a single basic block issue a "maybe" k
>>>>>> That seems wrong.   What you're looking for is a post-dominance 
>>>>>> relationship I think.   If the sink (free/delete) is 
>>>>>> post-dominated by the use, then it's a "must", if it's not 
>>>>>> post-dominated, then it's a maybe.  Of course, that means you need 
>>>>>> to build post-dominators.
>>>>>
>>>>> I'm sure you're right in general.  To avoid false positives
>>>>> the warning is very simplistic and only considers straight
>>>>> paths through the CFG, so I'm not sure this matters.  But
>>>>> I'm fine with using the post-dominance test instead if you
>>>>> thin it's worthwhile (it doesn't change any tests).
>>>>>
>>>>>>> +
>>>>>>> +      if (check_dangling
>>>>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>>>>> +        /* Avoid interfering with -Wreturn-local-addr (which 
>>>>>>> runs only
>>>>>>> +           with optimization enabled).  */
>>>>>>> +        continue;
>>>>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>>>>> removal of erroneous paths should gate any of this code.  ISTM 
>>>>>> that you're likely papering over a problem elsewhere.
>>>>>
>>>>> This code avoids issuing -Wdangling-pointer for problems that
>>>>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>>>>> case from Wreturn-local-addr-2.c:
>>>>>
>>>>>    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" } */
>>>>>    }
>>>>>
>>>>> Without the test we'd end up with
>>>>>
>>>>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>>>>
>>>>> in addition to -Wreturn-local-addr (and a whole slew of
>>>>> failures in the -Wreturn-local-addr tests).
>>>>>
>>>>> -Wreturn-local-addr only runs when
>>>>> flag_isolate_erroneous_paths_dereference is nonzero, so
>>>>> the conditional makes sure -Wdangling-pointer is issued when
>>>>> either the flag or -Wreturn-local-addr is disabled.  I think
>>>>> that works as expected (i.e., there's no problem elsewhere).
>>>>>
>>>>> I could have the code issue -Wdangling-pointer and suppress
>>>>> -Wreturn-local-addr but that doesn't seem right since
>>>>> the pointer hasn't gone out of scope yet at the point it's
>>>>> returned.
>>>>>
>>>>> Alternatively, I could change this instance of
>>>>> -Wdangling-pointer to -Wreturn-local-addr but that also
>>>>> doesn't seem like good design since we have a whole pass
>>>>> dedicated to the latter warning.
>>>>>
>>>>> I can't think of any other more elegant solutions but I'm open
>>>>> to suggestions.
>>>>>
>>>>> Martin
>>>>
>>>
>>
> 


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

* Re: [PATCH v2 1/2] add -Wuse-after-free
  2021-11-30 22:32       ` [PATCH v2 " Martin Sebor
  2021-12-07  0:50         ` PING " Martin Sebor
@ 2022-01-11 22:40         ` Jason Merrill
  2022-01-16  0:00           ` Martin Sebor
  2022-01-19 22:53           ` [PATCH v2 1/2] add -Wuse-after-free Jeff Law
  1 sibling, 2 replies; 34+ messages in thread
From: Jason Merrill @ 2022-01-11 22:40 UTC (permalink / raw)
  To: Martin Sebor, Jeff Law, gcc-patches

On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
> Attached is a revised patch with the following changes based
> on your comments:
> 
> 1) Set and use statement uids to determine which statement
>     precedes which in the same basic block.
> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
> 3) Use post-dominance to decide whether to use the "maybe"
>     phrasing vs a definite form.
> 
> David raised (and in our offline discussion today reiterated)
> an objection to the default setting of the option being
> the strictest.  I have not changed that in this revision.
> See my rationale for this choice in my reply below:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html

In the latest C2x draft I see in the list of undefined behavior

"The value of a pointer that refers to space deallocated by a call to 
the free or realloc function is used (7.22.3)."

So the case that would be technically undefined would be comparing the 
reallocated pointer to the old pointer which has been deallocated.

The C++ draft is more nuanced: it says, "When the end of the duration of 
a region of storage is reached, the values of all pointers representing 
the address of any part of that region of storage become invalid pointer 
values (6.8.3).  Indirection through an invalid pointer value and 
passing an invalid pointer value to a deallocation function have 
undefined behavior.  Any other use of an invalid pointer value has 
implementation-defined behavior."

So the case above is implementation-defined in C++, not undefined.

Let's put =2 in -Wall for now.

With that change, this and the -Wdangling-pointer patch are OK on Friday 
afternoon if there are no other comments before then.

> On 11/23/21 2:16 PM, Martin Sebor wrote:
>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>
>>>
>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>> made indeterminate by calls to deallocation functions like free
>>>> or C++ operator delete.  To control the conditions the warnings
>>>> are issued under the new -Wuse-after-free= option provides three
>>>> levels.  At the lowest level the warning triggers only for
>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>> in equality expressions.  Level 2 warns also for come conditional
>>>> uses, and level 3 also for uses in equality expressions.
>>>>
>>>> I debated whether to make level 2 or 3 the default included in
>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>> of both the problem and GCC's new ability to detect it: using
>>>> a pointer after it's been freed, even only in principle, by
>>>> a successful call to realloc, is undefined, and 2) because
>>>> it's trivial to lower the level either globally, or locally
>>>> by suppressing the warning around such misuses.
>>>>
>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>> due to comparing invalidated pointers for equality (i.e., level
>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>> and will see how the Glibc folks want to deal with theirs (I
>>>> track them in BZ #28521).
>>>>
>>>> The tests contain a number of xfails due to limitations I'm
>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>> I will open bugs for them before committing if I don't resolve
>>>> them in a followup.
>>>>
>>>> Martin
>>>>
>>>> gcc-63272-1.diff
>>>>
>>>> Add -Wuse-after-free.
>>>>
>>>> gcc/c-family/ChangeLog
>>>>
>>>>     * c.opt (-Wuse-after-free): New options.
>>>>
>>>> gcc/ChangeLog:
>>>>
>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>>     (pass_waccess::check_call_access): ...to this.
>>>>     (pass_waccess::check): Rename...
>>>>     (pass_waccess::check_block): ...to this.
>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>     (gimple_use_after_inval_p): New function.
>>>>     (get_realloc_lhs): New function.
>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>     (pointers_related_p): New function.
>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>
>>>> libcpp/ChangeLog:
>>>>
>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>
>>>> libiberty/ChangeLog:
>>>>
>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>
>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>> b/gcc/gimple-ssa-warn-access.cc
>>>> index 63fc27a1487..2065402a2b9 100644
>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>
>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>> (gcall *call)
>>>>       }
>>>>   }
>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>> pointer's use)
>>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>>> statement,
>>>> +   which is either a clobber or a deallocation call), or if they're in
>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>> +
>>>> +static bool
>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>> +              bool last_block = false)
>>>> +{
>>>> +  tree clobvar =
>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) 
>>>> : NULL_TREE;
>>>> +
>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>> +
>>>> +  if (inval_bb != use_bb)
>>>> +    {
>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>> +    return true;
>>>> +
>>>> +      if (!clobvar || !last_block)
>>>> +    return false;
>>>> +
>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>> +
>>>> +      auto_bitmap visited;
>>>> +
>>>> +      /* A use statement in the last basic block in a function or 
>>>> one that
>>>> +     falls through to it is after any other prior clobber of the used
>>>> +     variable unless it's followed by a clobber of the same 
>>>> variable. */
>>>> +      basic_block bb = use_bb;
>>>> +      while (bb != inval_bb
>>>> +         && single_succ_p (bb)
>>>> +         && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
>>>> +    {
>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>> +        /* Avoid cycles. */
>>>> +        return true;
>>>> +
>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>> +        {
>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>> +          if (gimple_clobber_p (stmt))
>>>> +        {
>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>> +            /* The use is followed by a clobber.  */
>>>> +            return false;
>>>> +        }
>>>> +        }
>>>> +
>>>> +      bb = single_succ (bb);
>>>> +      gsi = gsi_start_bb (bb);
>>>> +    }
>>>> +
>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>> +    }
>>> ?!?  I would have thought the block dominance test plus checking UIDs 
>>> if the two statements are in the same block would be all you need. 
>>> Can you elaborate more on what that hunk above is trying to do?
>>
>> The loop is entered only for -Wdangling-pointer.  It looks for
>> the first clobber of the CLOBVAR variable (one whose clobber
>> statement has been seen during the CFG traversal and whose use
>> is being validated) in the successors along the single edge
>> from the use block.  If the search finds a clobber, the use
>> is valid.  If it doesn't, the use is one of a variable having
>> gone out of scope (the clobber must be before the use).
>>
>> Among the cases the loop handles is the one in PR 63272
>> (the request for -Wdangling-pointer) where the use neither
>> follows the clobber in the same block nor dominated by it.
>>
>> There may be a way to optimize it somehow but because it's
>> a search I don't think a simple UID check alone would be
>> enough.
>>
>>>> +
>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>> +       gsi_next_nondebug (&si))
>>>> +    {
>>>> +      gimple *stmt = gsi_stmt (si);
>>>> +      if (stmt == use_stmt)
>>>> +    return true;
>>>> +    }
>>>> +
>>>> +  return false;
>>>> +}
>>> So from a compile-time standpoint, would it be better to to assign 
>>> UIDs to each statement so that within a block you can just compare 
>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>> statement domination within a block if we're going to be doing 
>>> multiple queries.
>>
>> I'd considered it but because statement UIDs don't exist at
>> the start of a pass, assigning them means either traversing all
>> statements in the whole CFG first, even in functions with no
>> deallocation calls or clobbers, or doing it lazily, after
>> the first such statement has been seen.  It might ultimately
>> be worthwhile if more warnings(*) end up relying on it but at
>> this point I'm not sure the optimization wouldn't end up slowing
>> things down on average.
>>
>> For some data, in a GCC bootstrap, each statement visited by
>> this loop is visited on average twice (2.2 times), and
>> the average sequence of statements traversed by the loop is
>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>> So still not sure it would be a win.
>>
>> Let me know if this is something you think I need to pursue at
>> this stage.
>>
>> [*] I think simple memory/resource leak detection might perhaps
>> be one.
>>
>>>> +
>>>> +/* Return true if P and Q point to the same object, and false if they
>>>> +   either don't or their relationship cannot be determined.  */
>>>> +
>>>> +static bool
>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>>>> +{
>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>> +    return false;
>>> Hmm, I guess that you don't need to worry about the case where P and 
>>> Q point to different elements within an array.  They point to 
>>> different final objects, though they do share a common enclosing 
>>> object. Similarly for P & Q pointing to different members within a 
>>> structure.
>>
>> Right.  The if statement is an optimization to avoid having to
>> determine the identity of the complete objects that P and Q
>> point to.  That's done by the calls to get_ref() below (for
>> complete objects; as you note, we don't care about subobjects
>> for this).
>>
>>>> +
>>>> +/* For a STMT either a call to a deallocation function or a 
>>>> clobber, warn
>>>> +   for uses of the pointer PTR it was called with (including its 
>>>> copies
>>>> +   or others derived from it by pointer arithmetic).  */
>>>> +
>>>> +void
>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>> +{
>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>> +
>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>> +
>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>> +     a single basic block issue a "maybe" k
>>> That seems wrong.   What you're looking for is a post-dominance 
>>> relationship I think.   If the sink (free/delete) is post-dominated 
>>> by the use, then it's a "must", if it's not post-dominated, then it's 
>>> a maybe.  Of course, that means you need to build post-dominators.
>>
>> I'm sure you're right in general.  To avoid false positives
>> the warning is very simplistic and only considers straight
>> paths through the CFG, so I'm not sure this matters.  But
>> I'm fine with using the post-dominance test instead if you
>> thin it's worthwhile (it doesn't change any tests).
>>
>>>> +
>>>> +      if (check_dangling
>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs only
>>>> +           with optimization enabled).  */
>>>> +        continue;
>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>> removal of erroneous paths should gate any of this code.  ISTM that 
>>> you're likely papering over a problem elsewhere.
>>
>> This code avoids issuing -Wdangling-pointer for problems that
>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>> case from Wreturn-local-addr-2.c:
>>
>>    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" } */
>>    }
>>
>> Without the test we'd end up with
>>
>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>
>> in addition to -Wreturn-local-addr (and a whole slew of
>> failures in the -Wreturn-local-addr tests).
>>
>> -Wreturn-local-addr only runs when
>> flag_isolate_erroneous_paths_dereference is nonzero, so
>> the conditional makes sure -Wdangling-pointer is issued when
>> either the flag or -Wreturn-local-addr is disabled.  I think
>> that works as expected (i.e., there's no problem elsewhere).
>>
>> I could have the code issue -Wdangling-pointer and suppress
>> -Wreturn-local-addr but that doesn't seem right since
>> the pointer hasn't gone out of scope yet at the point it's
>> returned.
>>
>> Alternatively, I could change this instance of
>> -Wdangling-pointer to -Wreturn-local-addr but that also
>> doesn't seem like good design since we have a whole pass
>> dedicated to the latter warning.
>>
>> I can't think of any other more elegant solutions but I'm open
>> to suggestions.
>>
>> Martin
> 


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

* Re: [PATCH v2 1/2] add -Wuse-after-free
  2022-01-11 22:40         ` Jason Merrill
@ 2022-01-16  0:00           ` Martin Sebor
  2022-03-26 20:35             ` Remove mysterious '-# Defining these options here in addition to common.opt is necessary' command-line option (was: [PATCH v2 1/2] add -Wuse-after-free) Thomas Schwinge
  2022-03-29  9:24             ` options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record " Thomas Schwinge
  2022-01-19 22:53           ` [PATCH v2 1/2] add -Wuse-after-free Jeff Law
  1 sibling, 2 replies; 34+ messages in thread
From: Martin Sebor @ 2022-01-16  0:00 UTC (permalink / raw)
  To: Jason Merrill, Jeff Law, gcc-patches

On 1/11/22 15:40, Jason Merrill wrote:
> On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
>> Attached is a revised patch with the following changes based
>> on your comments:
>>
>> 1) Set and use statement uids to determine which statement
>>     precedes which in the same basic block.
>> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
>> 3) Use post-dominance to decide whether to use the "maybe"
>>     phrasing vs a definite form.
>>
>> David raised (and in our offline discussion today reiterated)
>> an objection to the default setting of the option being
>> the strictest.  I have not changed that in this revision.
>> See my rationale for this choice in my reply below:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
> 
> In the latest C2x draft I see in the list of undefined behavior
> 
> "The value of a pointer that refers to space deallocated by a call to 
> the free or realloc function is used (7.22.3)."
> 
> So the case that would be technically undefined would be comparing the 
> reallocated pointer to the old pointer which has been deallocated.
> 
> The C++ draft is more nuanced: it says, "When the end of the duration of 
> a region of storage is reached, the values of all pointers representing 
> the address of any part of that region of storage become invalid pointer 
> values (6.8.3).  Indirection through an invalid pointer value and 
> passing an invalid pointer value to a deallocation function have 
> undefined behavior.  Any other use of an invalid pointer value has 
> implementation-defined behavior."
> 
> So the case above is implementation-defined in C++, not undefined.
> 
> Let's put =2 in -Wall for now.
> 
> With that change, this and the -Wdangling-pointer patch are OK on Friday 
> afternoon if there are no other comments before then.
I've adjusted both patches and pushed r12-6605 and r12-6606 after
retesting with a few packages.  In -Wdangling-pointer I reworded
the warning to refer to "unnamed temporary" instead of "compound
literal" since it triggers for those as well.

While rebuilding LLVM and a few its projects with the patches
I noticed the -Wdangling-pointer change exposes what seems like
a Ranger bug for one translation unit (it enters an infinite loop):
I opened pr104038 for it.

Martin

> 
>> On 11/23/21 2:16 PM, Martin Sebor wrote:
>>> On 11/22/21 6:32 PM, Jeff Law wrote:
>>>>
>>>>
>>>> On 11/1/2021 4:17 PM, Martin Sebor via Gcc-patches wrote:
>>>>> Patch 1 in the series detects a small subset of uses of pointers
>>>>> made indeterminate by calls to deallocation functions like free
>>>>> or C++ operator delete.  To control the conditions the warnings
>>>>> are issued under the new -Wuse-after-free= option provides three
>>>>> levels.  At the lowest level the warning triggers only for
>>>>> unconditional uses of freed pointers and doesn't warn for uses
>>>>> in equality expressions.  Level 2 warns also for come conditional
>>>>> uses, and level 3 also for uses in equality expressions.
>>>>>
>>>>> I debated whether to make level 2 or 3 the default included in
>>>>> -Wall.  I decided on 3 for two reasons: 1) to raise awareness
>>>>> of both the problem and GCC's new ability to detect it: using
>>>>> a pointer after it's been freed, even only in principle, by
>>>>> a successful call to realloc, is undefined, and 2) because
>>>>> it's trivial to lower the level either globally, or locally
>>>>> by suppressing the warning around such misuses.
>>>>>
>>>>> I've tested the patch on x86_64-linux and by building Glibc
>>>>> and Binutils/GDB.  It triggers a number of times in each, all
>>>>> due to comparing invalidated pointers for equality (i.e., level
>>>>> 3).  I have suppressed these in GCC (libiberty) by a #pragma,
>>>>> and will see how the Glibc folks want to deal with theirs (I
>>>>> track them in BZ #28521).
>>>>>
>>>>> The tests contain a number of xfails due to limitations I'm
>>>>> aware of.  I marked them pr?????? until the patch is approved.
>>>>> I will open bugs for them before committing if I don't resolve
>>>>> them in a followup.
>>>>>
>>>>> Martin
>>>>>
>>>>> gcc-63272-1.diff
>>>>>
>>>>> Add -Wuse-after-free.
>>>>>
>>>>> gcc/c-family/ChangeLog
>>>>>
>>>>>     * c.opt (-Wuse-after-free): New options.
>>>>>
>>>>> gcc/ChangeLog:
>>>>>
>>>>>     * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
>>>>>     OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
>>>>>     * diagnostic-spec.h (NW_DANGLING): New enumerator.
>>>>>     * doc/invoke.texi (-Wuse-after-free): Document new option.
>>>>>     * gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
>>>>>     (pass_waccess::check_call_access): ...to this.
>>>>>     (pass_waccess::check): Rename...
>>>>>     (pass_waccess::check_block): ...to this.
>>>>>     (pass_waccess::check_pointer_uses): New function.
>>>>>     (pass_waccess::gimple_call_return_arg): New function.
>>>>>     (pass_waccess::warn_invalid_pointer): New function.
>>>>>     (pass_waccess::check_builtin): Handle free and realloc.
>>>>>     (gimple_use_after_inval_p): New function.
>>>>>     (get_realloc_lhs): New function.
>>>>>     (maybe_warn_mismatched_realloc): New function.
>>>>>     (pointers_related_p): New function.
>>>>>     (pass_waccess::check_call): Call check_pointer_uses.
>>>>>     (pass_waccess::execute): Compute and free dominance info.
>>>>>
>>>>> libcpp/ChangeLog:
>>>>>
>>>>>     * files.c (_cpp_find_file): Substitute a valid pointer for
>>>>>     an invalid one to avoid -Wuse-0after-free.
>>>>>
>>>>> libiberty/ChangeLog:
>>>>>
>>>>>     * regex.c: Suppress -Wuse-after-free.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>>     * gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
>>>>>     * gcc.dg/Wmismatched-dealloc-3.c: Same.
>>>>>     * gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
>>>>>     * gcc.dg/attr-alloc_size-7.c: Same.
>>>>>     * c-c++-common/Wuse-after-free-2.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-3.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-4.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-5.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-6.c: New test.
>>>>>     * c-c++-common/Wuse-after-free-7.c: New test.
>>>>>     * c-c++-common/Wuse-after-free.c: New test.
>>>>>     * g++.dg/warn/Wdangling-pointer.C: New test.
>>>>>     * g++.dg/warn/Wmismatched-dealloc-3.C: New test.
>>>>>     * g++.dg/warn/Wuse-after-free.C: New test.
>>>>>
>>>>> diff --git a/gcc/gimple-ssa-warn-access.cc 
>>>>> b/gcc/gimple-ssa-warn-access.cc
>>>>> index 63fc27a1487..2065402a2b9 100644
>>>>> --- a/gcc/gimple-ssa-warn-access.cc
>>>>> +++ b/gcc/gimple-ssa-warn-access.cc
>>>>>
>>>>> @@ -3397,33 +3417,460 @@ pass_waccess::maybe_check_dealloc_call 
>>>>> (gcall *call)
>>>>>       }
>>>>>   }
>>>>> +/* Return true if either USE_STMT's basic block (that of a 
>>>>> pointer's use)
>>>>> +   is dominated by INVAL_STMT's (that of a pointer's invalidating 
>>>>> statement,
>>>>> +   which is either a clobber or a deallocation call), or if 
>>>>> they're in
>>>>> +   the same block, USE_STMT follows INVAL_STMT.  */
>>>>> +
>>>>> +static bool
>>>>> +gimple_use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
>>>>> +              bool last_block = false)
>>>>> +{
>>>>> +  tree clobvar =
>>>>> +    gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) 
>>>>> : NULL_TREE;
>>>>> +
>>>>> +  basic_block inval_bb = gimple_bb (inval_stmt);
>>>>> +  basic_block use_bb = gimple_bb (use_stmt);
>>>>> +
>>>>> +  if (inval_bb != use_bb)
>>>>> +    {
>>>>> +      if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
>>>>> +    return true;
>>>>> +
>>>>> +      if (!clobvar || !last_block)
>>>>> +    return false;
>>>>> +
>>>>> +      auto gsi = gsi_for_stmt (use_stmt);
>>>>> +
>>>>> +      auto_bitmap visited;
>>>>> +
>>>>> +      /* A use statement in the last basic block in a function or 
>>>>> one that
>>>>> +     falls through to it is after any other prior clobber of the used
>>>>> +     variable unless it's followed by a clobber of the same 
>>>>> variable. */
>>>>> +      basic_block bb = use_bb;
>>>>> +      while (bb != inval_bb
>>>>> +         && single_succ_p (bb)
>>>>> +         && !(single_succ_edge (bb)->flags & 
>>>>> (EDGE_EH|EDGE_DFS_BACK)))
>>>>> +    {
>>>>> +      if (!bitmap_set_bit (visited, bb->index))
>>>>> +        /* Avoid cycles. */
>>>>> +        return true;
>>>>> +
>>>>> +      for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
>>>>> +        {
>>>>> +          gimple *stmt = gsi_stmt (gsi);
>>>>> +          if (gimple_clobber_p (stmt))
>>>>> +        {
>>>>> +          if (clobvar == gimple_assign_lhs (stmt))
>>>>> +            /* The use is followed by a clobber.  */
>>>>> +            return false;
>>>>> +        }
>>>>> +        }
>>>>> +
>>>>> +      bb = single_succ (bb);
>>>>> +      gsi = gsi_start_bb (bb);
>>>>> +    }
>>>>> +
>>>>> +      return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
>>>>> +    }
>>>> ?!?  I would have thought the block dominance test plus checking 
>>>> UIDs if the two statements are in the same block would be all you 
>>>> need. Can you elaborate more on what that hunk above is trying to do?
>>>
>>> The loop is entered only for -Wdangling-pointer.  It looks for
>>> the first clobber of the CLOBVAR variable (one whose clobber
>>> statement has been seen during the CFG traversal and whose use
>>> is being validated) in the successors along the single edge
>>> from the use block.  If the search finds a clobber, the use
>>> is valid.  If it doesn't, the use is one of a variable having
>>> gone out of scope (the clobber must be before the use).
>>>
>>> Among the cases the loop handles is the one in PR 63272
>>> (the request for -Wdangling-pointer) where the use neither
>>> follows the clobber in the same block nor dominated by it.
>>>
>>> There may be a way to optimize it somehow but because it's
>>> a search I don't think a simple UID check alone would be
>>> enough.
>>>
>>>>> +
>>>>> +  for (auto si = gsi_for_stmt (inval_stmt); !gsi_end_p (si);
>>>>> +       gsi_next_nondebug (&si))
>>>>> +    {
>>>>> +      gimple *stmt = gsi_stmt (si);
>>>>> +      if (stmt == use_stmt)
>>>>> +    return true;
>>>>> +    }
>>>>> +
>>>>> +  return false;
>>>>> +}
>>>> So from a compile-time standpoint, would it be better to to assign 
>>>> UIDs to each statement so that within a block you can just compare 
>>>> the UIDs? That's a pretty standard way to deal with the problem of 
>>>> statement domination within a block if we're going to be doing 
>>>> multiple queries.
>>>
>>> I'd considered it but because statement UIDs don't exist at
>>> the start of a pass, assigning them means either traversing all
>>> statements in the whole CFG first, even in functions with no
>>> deallocation calls or clobbers, or doing it lazily, after
>>> the first such statement has been seen.  It might ultimately
>>> be worthwhile if more warnings(*) end up relying on it but at
>>> this point I'm not sure the optimization wouldn't end up slowing
>>> things down on average.
>>>
>>> For some data, in a GCC bootstrap, each statement visited by
>>> this loop is visited on average twice (2.2 times), and
>>> the average sequence of statements traversed by the loop is
>>> 2.65, with a maximum of 22 times and 18 statements, respectively.
>>> So still not sure it would be a win.
>>>
>>> Let me know if this is something you think I need to pursue at
>>> this stage.
>>>
>>> [*] I think simple memory/resource leak detection might perhaps
>>> be one.
>>>
>>>>> +
>>>>> +/* Return true if P and Q point to the same object, and false if they
>>>>> +   either don't or their relationship cannot be determined.  */
>>>>> +
>>>>> +static bool
>>>>> +pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
>>>>> +{
>>>>> +  if (!ptr_derefs_may_alias_p (p, q))
>>>>> +    return false;
>>>> Hmm, I guess that you don't need to worry about the case where P and 
>>>> Q point to different elements within an array.  They point to 
>>>> different final objects, though they do share a common enclosing 
>>>> object. Similarly for P & Q pointing to different members within a 
>>>> structure.
>>>
>>> Right.  The if statement is an optimization to avoid having to
>>> determine the identity of the complete objects that P and Q
>>> point to.  That's done by the calls to get_ref() below (for
>>> complete objects; as you note, we don't care about subobjects
>>> for this).
>>>
>>>>> +
>>>>> +/* For a STMT either a call to a deallocation function or a 
>>>>> clobber, warn
>>>>> +   for uses of the pointer PTR it was called with (including its 
>>>>> copies
>>>>> +   or others derived from it by pointer arithmetic).  */
>>>>> +
>>>>> +void
>>>>> +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
>>>>> +{
>>>>> +  gcc_assert (TREE_CODE (ptr) == SSA_NAME);
>>>>> +
>>>>> +  const bool check_dangling = !is_gimple_call (stmt);
>>>>> +  basic_block stmt_bb = gimple_bb (stmt);
>>>>> +
>>>>> +  /* If the deallocation (or clobber) statement dominates more than
>>>>> +     a single basic block issue a "maybe" k
>>>> That seems wrong.   What you're looking for is a post-dominance 
>>>> relationship I think.   If the sink (free/delete) is post-dominated 
>>>> by the use, then it's a "must", if it's not post-dominated, then 
>>>> it's a maybe.  Of course, that means you need to build post-dominators.
>>>
>>> I'm sure you're right in general.  To avoid false positives
>>> the warning is very simplistic and only considers straight
>>> paths through the CFG, so I'm not sure this matters.  But
>>> I'm fine with using the post-dominance test instead if you
>>> thin it's worthwhile (it doesn't change any tests).
>>>
>>>>> +
>>>>> +      if (check_dangling
>>>>> +          && gimple_code (use_stmt) == GIMPLE_RETURN
>>>>> +          && optimize && flag_isolate_erroneous_paths_dereference)
>>>>> +        /* Avoid interfering with -Wreturn-local-addr (which runs 
>>>>> only
>>>>> +           with optimization enabled).  */
>>>>> +        continue;
>>>> Umm, that looks like a hack.  I can't think of a good reason why 
>>>> removal of erroneous paths should gate any of this code.  ISTM that 
>>>> you're likely papering over a problem elsewhere.
>>>
>>> This code avoids issuing -Wdangling-pointer for problems that
>>> will later be diagnosed by -Wreturn-local-addr.  E.g., in this
>>> case from Wreturn-local-addr-2.c:
>>>
>>>    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" } */
>>>    }
>>>
>>> Without the test we'd end up with
>>>
>>>    warning: using dangling pointer ‘p’ to ‘a’ [-Wdangling-pointer=]
>>>
>>> in addition to -Wreturn-local-addr (and a whole slew of
>>> failures in the -Wreturn-local-addr tests).
>>>
>>> -Wreturn-local-addr only runs when
>>> flag_isolate_erroneous_paths_dereference is nonzero, so
>>> the conditional makes sure -Wdangling-pointer is issued when
>>> either the flag or -Wreturn-local-addr is disabled.  I think
>>> that works as expected (i.e., there's no problem elsewhere).
>>>
>>> I could have the code issue -Wdangling-pointer and suppress
>>> -Wreturn-local-addr but that doesn't seem right since
>>> the pointer hasn't gone out of scope yet at the point it's
>>> returned.
>>>
>>> Alternatively, I could change this instance of
>>> -Wdangling-pointer to -Wreturn-local-addr but that also
>>> doesn't seem like good design since we have a whole pass
>>> dedicated to the latter warning.
>>>
>>> I can't think of any other more elegant solutions but I'm open
>>> to suggestions.
>>>
>>> Martin
>>
> 


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

* Re: PING 4 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2022-01-10 21:51           ` PING 4 " Martin Sebor
@ 2022-01-17 13:46             ` Stephan Bergmann
  2022-01-17 19:14               ` Martin Sebor
  0 siblings, 1 reply; 34+ messages in thread
From: Stephan Bergmann @ 2022-01-17 13:46 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/01/2022 22:51, Martin Sebor via Gcc-patches wrote:
> Last ping for this stage 1 feature before stage 3 ends:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html

This hits somewhat unexpectedly at (test case reduced from a hit in 
LibreOffice)

> $ cat test.cc
> #include <initializer_list>
> struct S1 {
>     S1(int);
>     ~S1();
> };
> struct S2 { S2(std::initializer_list<S1>); };
> S2 f1();
> S2 f2(bool b) { return b ? f1() : S2{0}; }

> $ g++ -Wdangling-pointer -c test.cc
> test.cc: In function ‘S2 f2(bool)’:
> test.cc:8:42: warning: dangling pointer to an unnamed temporary may be used [-Wdangling-pointer=]
>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>       |                                          ^
> test.cc:8:39: note: unnamed temporary defined here
>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>       |                                       ^
> test.cc:8:42: warning: dangling pointer to an unnamed temporary may be used [-Wdangling-pointer=]
>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>       |                                          ^
> test.cc:8:39: note: unnamed temporary defined here
>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>       |                                       ^


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

* Re: PING 4 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2022-01-17 13:46             ` Stephan Bergmann
@ 2022-01-17 19:14               ` Martin Sebor
  2022-01-19 14:03                 ` Stephan Bergmann
  0 siblings, 1 reply; 34+ messages in thread
From: Martin Sebor @ 2022-01-17 19:14 UTC (permalink / raw)
  To: Stephan Bergmann, gcc-patches

On 1/17/22 06:46, Stephan Bergmann wrote:
> On 10/01/2022 22:51, Martin Sebor via Gcc-patches wrote:
>> Last ping for this stage 1 feature before stage 3 ends:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/585819.html
> 
> This hits somewhat unexpectedly at (test case reduced from a hit in 
> LibreOffice)

Thanks for the small test case!  It seems like the PHI handling
(conditionals) might overly simplistic.  Let me look into it.

I tried to set up OpenOffice for testing with the latest GCC but
couldn't get the build to finish (it failed downloading some
unavailable prerequisites).  I don't remember what problem I ran
into with LibreOffice; it was before I upgraded to Fedora 35 just
a couple of weeks ago.  Let me retry again (the build is still
downloading tarballs).

In the meantime, do you have any tips or suggestions getting it
set up that aren't on the instructions page below?  (Especially
for using an alternate compiler and non-default options.)

https://wiki.documentfoundation.org/Development/BuildingOnLinux#Fedora.2FRedHat

Martin

> 
>> $ cat test.cc
>> #include <initializer_list>
>> struct S1 {
>>     S1(int);
>>     ~S1();
>> };
>> struct S2 { S2(std::initializer_list<S1>); };
>> S2 f1();
>> S2 f2(bool b) { return b ? f1() : S2{0}; }
> 
>> $ g++ -Wdangling-pointer -c test.cc
>> test.cc: In function ‘S2 f2(bool)’:
>> test.cc:8:42: warning: dangling pointer to an unnamed temporary may be 
>> used [-Wdangling-pointer=]
>>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>>       |                                          ^
>> test.cc:8:39: note: unnamed temporary defined here
>>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>>       |                                       ^
>> test.cc:8:42: warning: dangling pointer to an unnamed temporary may be 
>> used [-Wdangling-pointer=]
>>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>>       |                                          ^
>> test.cc:8:39: note: unnamed temporary defined here
>>     8 | S2 f2(bool b) { return b ? f1() : S2{0}; }
>>       |                                       ^
> 


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

* Re: PING 4 [PATCH v2 2/2] add -Wdangling-pointer [PR #63272]
  2022-01-17 19:14               ` Martin Sebor
@ 2022-01-19 14:03                 ` Stephan Bergmann
  0 siblings, 0 replies; 34+ messages in thread
From: Stephan Bergmann @ 2022-01-19 14:03 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On 17/01/2022 20:14, Martin Sebor wrote:
> I tried to set up OpenOffice for testing with the latest GCC but
> couldn't get the build to finish (it failed downloading some
> unavailable prerequisites).  I don't remember what problem I ran
> into with LibreOffice; it was before I upgraded to Fedora 35 just
> a couple of weeks ago.  Let me retry again (the build is still
> downloading tarballs).
> 
> In the meantime, do you have any tips or suggestions getting it
> set up that aren't on the instructions page below?  (Especially
> for using an alternate compiler and non-default options.)
> 
> https://wiki.documentfoundation.org/Development/BuildingOnLinux#Fedora.2FRedHat 

Building LibreOffice from source should be relatively easy these days, 
esp. on Linux.  Let me know if you have any specific issues.  What I do 
to build against a GCC other than the system one is to include the two lines

CC=/path/to/gcc
CXX=/path/to/g++

in autogen.input.

(And if you have any issues building LibreOffice, I guess you would have 
an even worse experience trying to build OpenOffice.  I for one never 
looked back.)


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

* Re: [PATCH v2 1/2] add -Wuse-after-free
  2022-01-11 22:40         ` Jason Merrill
  2022-01-16  0:00           ` Martin Sebor
@ 2022-01-19 22:53           ` Jeff Law
  1 sibling, 0 replies; 34+ messages in thread
From: Jeff Law @ 2022-01-19 22:53 UTC (permalink / raw)
  To: Jason Merrill, Martin Sebor, gcc-patches



On 1/11/2022 3:40 PM, Jason Merrill wrote:
> On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
>> Attached is a revised patch with the following changes based
>> on your comments:
>>
>> 1) Set and use statement uids to determine which statement
>>     precedes which in the same basic block.
>> 2) Avoid testing flag_isolate_erroneous_paths_dereference.
>> 3) Use post-dominance to decide whether to use the "maybe"
>>     phrasing vs a definite form.
>>
>> David raised (and in our offline discussion today reiterated)
>> an objection to the default setting of the option being
>> the strictest.  I have not changed that in this revision.
>> See my rationale for this choice in my reply below:
>> https://gcc.gnu.org/pipermail/gcc-patches/2021-November/583176.html
>
> In the latest C2x draft I see in the list of undefined behavior
>
> "The value of a pointer that refers to space deallocated by a call to 
> the free or realloc function is used (7.22.3)."
>
> So the case that would be technically undefined would be comparing the 
> reallocated pointer to the old pointer which has been deallocated.
>
> The C++ draft is more nuanced: it says, "When the end of the duration 
> of a region of storage is reached, the values of all pointers 
> representing the address of any part of that region of storage become 
> invalid pointer values (6.8.3).  Indirection through an invalid 
> pointer value and passing an invalid pointer value to a deallocation 
> function have undefined behavior.  Any other use of an invalid pointer 
> value has implementation-defined behavior."
>
> So the case above is implementation-defined in C++, not undefined.
>
> Let's put =2 in -Wall for now.
>
> With that change, this and the -Wdangling-pointer patch are OK on 
> Friday afternoon if there are no other comments before then.
THanks for picking this up.  I've been busier than expected the last 
several weeks.

jeff


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

* Remove mysterious '-# Defining these options here in addition to common.opt is necessary' command-line option (was: [PATCH v2 1/2] add -Wuse-after-free)
  2022-01-16  0:00           ` Martin Sebor
@ 2022-03-26 20:35             ` Thomas Schwinge
  2022-03-29  9:24             ` options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record " Thomas Schwinge
  1 sibling, 0 replies; 34+ messages in thread
From: Thomas Schwinge @ 2022-03-26 20:35 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

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

Hi!

On 2022-01-15T17:00:11-0700, Martin Sebor via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:
> On 1/11/22 15:40, Jason Merrill wrote:
>> On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
>>> [default setting of the option]

>> Let's put =2 in -Wall for now.

> I've adjusted [...] and pushed r12-6605 [...]

Pushed to master branch commit 43911ddd18b97d8ebd17d2959f36efa539d359b7
"Remove mysterious '-# Defining these options here in addition to
common.opt is necessary' command-line option", see attached.  ;'-)


Grüße
 Thomas


-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

[-- Attachment #2: 0001-Remove-mysterious-Defining-these-options-here-in-add.patch --]
[-- Type: text/x-diff, Size: 3458 bytes --]

From 43911ddd18b97d8ebd17d2959f36efa539d359b7 Mon Sep 17 00:00:00 2001
From: Thomas Schwinge <thomas@codesourcery.com>
Date: Thu, 24 Mar 2022 22:34:30 +0100
Subject: [PATCH] Remove mysterious '-# Defining these options here in addition
 to common.opt is necessary' command-line option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Before:

    $ [...]/gcc '-# Defining these options here in addition to common.opt is necessary' -S -x c /dev/null && echo MYSTERIOUS
    MYSTERIOUS

After:

    $ [...]/gcc '-# Defining these options here in addition to common.opt is necessary' -S -x c /dev/null && echo MYSTERIOUS
    gcc: error: unrecognized command-line option ‘-# Defining these options here in addition to common.opt is necessary’

This commit changes:

    --- [...]/build-gcc/gcc/optionlist	2022-03-24 22:12:07.936746648 +0100
    +++ [...]/build-gcc/gcc/optionlist	2022-03-24 22:30:06.976737341 +0100
    @@ -1,4 +1,3 @@
    -# Defining these options here in addition to common.opt is necessary\x1c# in order for the default -Wall setting of -Wuse-after-free=2 to take\x1c# effect.
     ###\x1cDriver
     -all-warnings\x1cAda AdaWhy AdaSCIL Alias(Wall)
     -all-warnings\x1cC ObjC C++ ObjC++ Warning Alias(Wall)
    [...]

    --- [...]/build-gcc/gcc/options.cc	2022-03-24 22:12:09.548727738 +0100
    +++ [...]/build-gcc/gcc/options.cc	2022-03-24 22:30:08.904727249 +0100
    @@ -3222,15 +3222,6 @@
     const struct cl_option cl_options[] =
     {
      /* [0] = */ {
    -    "-# Defining these options here in addition to common.opt is necessary",
    -    "# effect.",
    -    NULL,
    -    NULL,
    -    NULL, NULL, N_OPTS, N_OPTS, 68, /* .neg_idx = */ -1,
    -    0,
    -    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    -    (unsigned short) -1, 0, CLVC_INTEGER, 0, -1, -1 },
    - /* [1] = */ {
         "-###",
         NULL,
         NULL,
    [...]

..., and re-numbers all following options.

    --- [...]/build-gcc/gcc/options.h	2022-03-21 23:24:25.894226828 +0100
    +++ [...]/build-gcc/gcc/options.h	2022-03-24 22:30:07.288735708 +0100
    @@ -9753,2118 +9753,2117 @@

     enum opt_code
     {
    -  OPT___Defining_these_options_here_in_addition_to_common_opt_is_necessary = 0,/* -# Defining these options here in addition to common.opt is necessary */
    [...]

..., and likewise re-numbers all following options.

Clean-up for commit 671a283636de75f7ed638ee6b01ed2d44361b8b6
"Add -Wuse-after-free [PR80532]".

	gcc/c-family/
	* c.opt: Properly quote comment.
---
 gcc/c-family/c.opt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 9a4828ebe37..790d47caf0a 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1377,9 +1377,9 @@ Wunused-const-variable=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_unused_const_variable) Warning LangEnabledBy(C ObjC,Wunused-variable, 1, 0) IntegerRange(0, 2)
 Warn when a const variable is unused.
 
-# Defining these options here in addition to common.opt is necessary
-# in order for the default -Wall setting of -Wuse-after-free=2 to take
-# effect.
+; Defining these options here in addition to common.opt is necessary
+; in order for the default -Wall setting of -Wuse-after-free=2 to take
+; effect.
 
 Wuse-after-free
 LangEnabledBy(C ObjC C++ LTO ObjC++)
-- 
2.35.1


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

* options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record (was: [PATCH v2 1/2] add -Wuse-after-free)
  2022-01-16  0:00           ` Martin Sebor
  2022-03-26 20:35             ` Remove mysterious '-# Defining these options here in addition to common.opt is necessary' command-line option (was: [PATCH v2 1/2] add -Wuse-after-free) Thomas Schwinge
@ 2022-03-29  9:24             ` Thomas Schwinge
  2022-03-29 15:15               ` Martin Sebor
  2022-03-29 18:00               ` Joseph Myers
  1 sibling, 2 replies; 34+ messages in thread
From: Thomas Schwinge @ 2022-03-29  9:24 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches, Joseph Myers

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

Hi!

On 2022-01-15T17:00:11-0700, Martin Sebor via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:
> On 1/11/22 15:40, Jason Merrill wrote:
>> On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
>>> [default setting of the option]

>> Let's put =2 in -Wall for now.

> I've adjusted [...] and pushed r12-6605 [...]

That's from commit 671a283636de75f7ed638ee6b01ed2d44361b8b6
"Add -Wuse-after-free [PR80532]":

| --- gcc/common.opt
| +++ gcc/common.opt
| [...]
| +Wuse-after-free
| +Common Var(warn_use_after_free) Warning
| +Warn for uses of pointers to deallocated strorage.
| +
| +Wuse-after-free=
| +Common Joined RejectNegative UInteger Var(warn_use_after_free) Warning IntegerRange(0, 3)
| +Warn for uses of pointers to deallocated strorage.
| [...]

| --- gcc/c-family/c.opt
| +++ gcc/c-family/c.opt
| [...]
| +# Defining these options here in addition to common.opt is necessary
| +# in order for the default -Wall setting of -Wuse-after-free=2 to take
| +# effect.
| +
| +Wuse-after-free
| +LangEnabledBy(C ObjC C++ LTO ObjC++)
| +; in common.opt
| +
| +Wuse-after-free=
| +LangEnabledBy(C ObjC C++ LTO ObjC++, Wall,2,0)
| +; in common.opt
| [...]

OK to push the attached "options: Remove
'gcc/c-family/c.opt:Wuse-after-free' option definition record"?


Grüße
 Thomas


-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-options-Remove-gcc-c-family-c.opt-Wuse-after-free-op.patch --]
[-- Type: text/x-diff, Size: 1672 bytes --]

From eda478e23a611611353d22e11727a797b9f321f9 Mon Sep 17 00:00:00 2001
From: Thomas Schwinge <thomas@codesourcery.com>
Date: Sat, 26 Mar 2022 22:07:54 +0100
Subject: [PATCH] options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option
 definition record

A one-argument form of the 'LangEnabledBy' option property isn't defined,
and effectively appears to be a no-op.  Removing that one, the
'gcc/c-family/c.opt:Wuse-after-free' option definition record becomes
empty, and doesn't add anything over 'gcc/common.opt:Wuse-after-free', and
may thus be removed entirely.  This only changes 'build-gcc/gcc/optionlist'
accordingly, but no other generated files.

Clean-up after recent commit 671a283636de75f7ed638ee6b01ed2d44361b8b6
"Add -Wuse-after-free [PR80532]".

	gcc/c-family/
	* c.opt (Wuse-after-free): Remove.
---
 gcc/c-family/c.opt | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 3c2ec7744b0..1034a1b3946 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1373,14 +1373,10 @@ Wunused-const-variable=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_unused_const_variable) Warning LangEnabledBy(C ObjC,Wunused-variable, 1, 0) IntegerRange(0, 2)
 Warn when a const variable is unused.
 
-; Defining these options here in addition to common.opt is necessary
+; Defining this option here in addition to common.opt is necessary
 ; in order for the default -Wall setting of -Wuse-after-free=2 to take
 ; effect.
 
-Wuse-after-free
-LangEnabledBy(C ObjC C++ LTO ObjC++)
-; in common.opt
-
 Wuse-after-free=
 LangEnabledBy(C ObjC C++ LTO ObjC++, Wall,2,0)
 ; in common.opt
-- 
2.25.1


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

* Re: options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record (was: [PATCH v2 1/2] add -Wuse-after-free)
  2022-03-29  9:24             ` options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record " Thomas Schwinge
@ 2022-03-29 15:15               ` Martin Sebor
  2022-03-29 18:00               ` Joseph Myers
  1 sibling, 0 replies; 34+ messages in thread
From: Martin Sebor @ 2022-03-29 15:15 UTC (permalink / raw)
  To: Thomas Schwinge, gcc-patches, Joseph Myers

On 3/29/22 03:24, Thomas Schwinge wrote:
> Hi!
> 
> On 2022-01-15T17:00:11-0700, Martin Sebor via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:
>> On 1/11/22 15:40, Jason Merrill wrote:
>>> On 11/30/21 17:32, Martin Sebor via Gcc-patches wrote:
>>>> [default setting of the option]
> 
>>> Let's put =2 in -Wall for now.
> 
>> I've adjusted [...] and pushed r12-6605 [...]
> 
> That's from commit 671a283636de75f7ed638ee6b01ed2d44361b8b6
> "Add -Wuse-after-free [PR80532]":
> 
> | --- gcc/common.opt
> | +++ gcc/common.opt
> | [...]
> | +Wuse-after-free
> | +Common Var(warn_use_after_free) Warning
> | +Warn for uses of pointers to deallocated strorage.
> | +
> | +Wuse-after-free=
> | +Common Joined RejectNegative UInteger Var(warn_use_after_free) Warning IntegerRange(0, 3)
> | +Warn for uses of pointers to deallocated strorage.
> | [...]
> 
> | --- gcc/c-family/c.opt
> | +++ gcc/c-family/c.opt
> | [...]
> | +# Defining these options here in addition to common.opt is necessary
> | +# in order for the default -Wall setting of -Wuse-after-free=2 to take
> | +# effect.
> | +
> | +Wuse-after-free
> | +LangEnabledBy(C ObjC C++ LTO ObjC++)
> | +; in common.opt
> | +
> | +Wuse-after-free=
> | +LangEnabledBy(C ObjC C++ LTO ObjC++, Wall,2,0)
> | +; in common.opt
> | [...]
> 
> OK to push the attached "options: Remove
> 'gcc/c-family/c.opt:Wuse-after-free' option definition record"?

It's fine with me if it passes tests.  I remember noticing a subtle
distinction in how option aliases are sometimes treated in #pragma
GCC diagnostic but not the exact details.  I added tests to make
sure it has the expected effect without the trailing =.  See
the comment in c-c++-common/Wuse-after-free.c.

Thanks
Martin

> 
> 
> Grüße
>   Thomas
> 
> 
> -----------------
> Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955


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

* Re: options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record (was: [PATCH v2 1/2] add -Wuse-after-free)
  2022-03-29  9:24             ` options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record " Thomas Schwinge
  2022-03-29 15:15               ` Martin Sebor
@ 2022-03-29 18:00               ` Joseph Myers
  1 sibling, 0 replies; 34+ messages in thread
From: Joseph Myers @ 2022-03-29 18:00 UTC (permalink / raw)
  To: Thomas Schwinge; +Cc: Martin Sebor, gcc-patches

On Tue, 29 Mar 2022, Thomas Schwinge wrote:

> | --- gcc/c-family/c.opt
> | +++ gcc/c-family/c.opt
> | [...]
> | +# Defining these options here in addition to common.opt is necessary
> | +# in order for the default -Wall setting of -Wuse-after-free=2 to take
> | +# effect.
> | +
> | +Wuse-after-free
> | +LangEnabledBy(C ObjC C++ LTO ObjC++)
> | +; in common.opt
> | +
> | +Wuse-after-free=
> | +LangEnabledBy(C ObjC C++ LTO ObjC++, Wall,2,0)
> | +; in common.opt
> | [...]
> 
> OK to push the attached "options: Remove
> 'gcc/c-family/c.opt:Wuse-after-free' option definition record"?

OK.

-- 
Joseph S. Myers
joseph@codesourcery.com

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

end of thread, other threads:[~2022-03-29 18:00 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-01 22:15 [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
2021-11-01 22:17 ` [PATCH 1/2] add -Wuse-after-free Martin Sebor
2021-11-02  5:32   ` Eric Gallager
2021-11-02 17:09     ` Martin Sebor
2021-11-02 22:29   ` David Malcolm
2021-11-03  0:22     ` Martin Sebor
2021-11-23  1:32   ` Jeff Law
2021-11-23 21:16     ` Martin Sebor
2021-11-30 22:32       ` [PATCH v2 " Martin Sebor
2021-12-07  0:50         ` PING " Martin Sebor
2021-12-13 16:48           ` PING 2 " Martin Sebor
2022-01-04 18:01             ` PING 3 " Martin Sebor
2022-01-10 21:58               ` PING 4 " Martin Sebor
2022-01-11 22:40         ` Jason Merrill
2022-01-16  0:00           ` Martin Sebor
2022-03-26 20:35             ` Remove mysterious '-# Defining these options here in addition to common.opt is necessary' command-line option (was: [PATCH v2 1/2] add -Wuse-after-free) Thomas Schwinge
2022-03-29  9:24             ` options: Remove 'gcc/c-family/c.opt:Wuse-after-free' option definition record " Thomas Schwinge
2022-03-29 15:15               ` Martin Sebor
2022-03-29 18:00               ` Joseph Myers
2022-01-19 22:53           ` [PATCH v2 1/2] add -Wuse-after-free Jeff Law
2021-11-01 22:18 ` [PATCH 2/2] add -Wdangling-pointer [PR #63272] Martin Sebor
2021-11-02  7:40   ` Eric Gallager
2021-11-02 18:38     ` Martin Sebor
2021-11-30 22:55   ` [PATCH v2 " Martin Sebor
2021-12-07  0:51     ` PING " Martin Sebor
2021-12-13 16:50       ` PING 2 " Martin Sebor
2022-01-04 18:02         ` PING 3 " Martin Sebor
2022-01-10 21:51           ` PING 4 " Martin Sebor
2022-01-17 13:46             ` Stephan Bergmann
2022-01-17 19:14               ` Martin Sebor
2022-01-19 14:03                 ` Stephan Bergmann
2021-11-08 22:41 ` PING [PATCH 0/2] provide simple detection of indeterminate pointers Martin Sebor
2021-11-15 16:47   ` PING 2 " Martin Sebor
2021-11-22 16:41     ` PING 3 " 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).