diff mbox

[RFC] Speed-up use-after-scope (re-writing to SSA)

Message ID d1728bae-3225-9c09-12f8-5e07d33049e3@suse.cz
State New
Headers show

Commit Message

Martin Liška Nov. 22, 2016, 11:55 a.m. UTC
On 11/16/2016 05:28 PM, Jakub Jelinek wrote:
> On Wed, Nov 16, 2016 at 05:01:31PM +0100, Martin Liška wrote:
>> +  use_operand_p use_p;
>> +  imm_use_iterator imm_iter;
>> +  FOR_EACH_IMM_USE_FAST (use_p, imm_iter, poisoned_var)
>> +    {
>> +      gimple *use = USE_STMT (use_p);
>> +      if (is_gimple_debug (use))
>> +	continue;
>> +
>> +      built_in_function b = (recover_p
>> +			     ? BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE_NOABORT
>> +			     : BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE);
>> +      tree fun = builtin_decl_implicit (b);
>> +      pretty_printer pp;
>> +      pp_tree_identifier (&pp, DECL_NAME (var_decl));
>> +
>> +      gcall *call = gimple_build_call (fun, 2, asan_pp_string (&pp),
>> +				       DECL_SIZE_UNIT (var_decl));
>> +      gimple_set_location (call, gimple_location (use));
>> +
>> +      /* The USE can be a gimple PHI node.  If so, insert the call on
>> +	 all edges leading to the PHI node.  */
>> +      if (is_a <gphi *> (use))
>> +	{
>> +	  gphi * phi = dyn_cast<gphi *> (use);
> 
> No space after *.

Done.

> 
>> +	  for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i)
>> +	    if (gimple_phi_arg_def (phi, i) == poisoned_var)
>> +	      {
>> +		edge e = gimple_phi_arg_edge (phi, i);
>> +		gsi_insert_seq_on_edge (e, call);
>> +		*need_commit_edge_insert = true;
> 
> You clearly don't have a sufficient testsuite coverage for this,
> because this won't really work if you have more than one phi
> argument equal to poisoned_var.  Inserting the same gimple stmt
> into multiple places can't really work.  I bet you want to set
> call to NULL after the gsi_insert_seq_on_edge and before that
> call if (call == NULL) { call = gimple_build_call (...); gimple_set_location (...); }
> Or maybe gimple_copy for the 2nd etc. would work too, dunno.

I see, fixed by using gimple_copy functionality.

> 
>> +	      }
>> +	}
>> +      else
>> +	{
>> +	  gimple_stmt_iterator gsi = gsi_for_stmt (use);
>> +	  gsi_insert_before (&gsi, call, GSI_NEW_STMT);
>> +	}
>> +    }
>> +
>> +  gimple *nop = gimple_build_nop ();
>> +  SSA_NAME_IS_DEFAULT_DEF (poisoned_var) = true;
>> +  SSA_NAME_DEF_STMT (poisoned_var) = nop;
>> +  gsi_replace (iter, nop, GSI_NEW_STMT);
> 
> The last argument of gsi_replace is a bool, not GSI_*.
> But not sure how this will work anyway, I think SSA_NAME_IS_DEFAULT_DEF
> are supposed to have SSA_NAME_DEF_STMT a GIMPLE_NOP that doesn't
> have bb set, while you are putting it into the stmt sequence.
> Shouldn't you just gsi_remove iter instead?

gsi_remove does not work as a SSA name would lost a defining statement. However
setting SSA_NAME_DEF_STMT (poisoned_var) = gimple_build_nop () and removing the stmt
works fine. I haven't known that it can't belong to a BB.

Maybe we can add a verifier for that?

That can be eventually done independently.

> 
> Otherwise LGTM, but please post the asan patch to llvm-commits
> or through their web review interface.

Good, I'm going to insert the patch to the tool.

Thanks,
Martin

> 
> 	Jakub
>

Comments

Martin Liška Nov. 23, 2016, 1:57 p.m. UTC | #1
I started review process in libsanitizer: https://reviews.llvm.org/D26965
And I have a question that was asked in the review: can we distinguish between load and store
in case of having usage of ASAN_POISON?

Load looks as follows:

int
main (int argc, char **argv)
{
  char *ptr;

  if (argc != 12312)
  {
    char my_char;
    ptr = &my_char;
  }

  return *ptr;
}

main (int argc, char * * argv)
{
  char my_char;
  int _5;

  <bb 2>:
  if (argc_1(D) != 12312)
    goto <bb 3>;
  else
    goto <bb 5>;

  <bb 5>:
  goto <bb 4>;

  <bb 3>:
  my_char_8 = ASAN_POISON ();

  <bb 4>:
  # my_char_6 = PHI <my_char_7(D)(5), my_char_8(3)>
  _5 = (int) my_char_6;
  return _5;

}

however doing a store:
int
main (int argc, char **argv)
{
  char *ptr;

  if (argc != 12312)
  {
    char my_char;
    ptr = &my_char;
  }

  *ptr = 0;
  return 0;
}

main (int argc, char * * argv)
{
  <bb 2>:
  if (argc_1(D) != 12312)
    goto <bb 3>;
  else
    goto <bb 5>;

  <bb 5>:
  goto <bb 4>;

  <bb 3>:
  ASAN_POISON ();

  <bb 4>:
  return 0;

}

leads to a situation, where LHS of ASAN_POISON assignment is identified as overwritten and eventually
we see just ASAN_POISON call. This is currently removed in sanopt pass, but I'm wondering whether it's
valid optimization or not in this context?

Thanks,
Martin
Jakub Jelinek Nov. 23, 2016, 2:13 p.m. UTC | #2
On Wed, Nov 23, 2016 at 02:57:07PM +0100, Martin Liška wrote:
> I started review process in libsanitizer: https://reviews.llvm.org/D26965
> And I have a question that was asked in the review: can we distinguish between load and store
> in case of having usage of ASAN_POISON?

I think with ASAN_POISON it is indeed just loads from after scope that can
be caught, a store overwrites the variable with a new value and when turning
the store after we make the var no longer addressable into SSA form, we
loose information about the out of scope store.  Furthermore, if there is
first a store and then a read, like:
  if (argc != 12312)
    {
      char my_char;
      ptr = &my_char;
    }
  *ptr = i + 26;
  return *ptr;
we don't notice even the read.  Not sure what could be done against that
though.  I think we'd need to hook into the into-ssa framework, there it
should know the current value of the variable at the point of the store is
result of ASAN_POISON and be able to instead of turning that
  my_char = _23;
into
  my_char_35 = _23;
turn it into:
  my_char_35 = ASAN_POISON (_23);
which would represent after scope store into my_char.

Not really familiar with into-ssa though to know where to do it.

	Jakub
Martin Liška Dec. 1, 2016, 4:30 p.m. UTC | #3
On 11/23/2016 03:13 PM, Jakub Jelinek wrote:
> On Wed, Nov 23, 2016 at 02:57:07PM +0100, Martin Liška wrote:
>> I started review process in libsanitizer: https://reviews.llvm.org/D26965
>> And I have a question that was asked in the review: can we distinguish between load and store
>> in case of having usage of ASAN_POISON?
> 
> I think with ASAN_POISON it is indeed just loads from after scope that can
> be caught, a store overwrites the variable with a new value and when turning
> the store after we make the var no longer addressable into SSA form, we
> loose information about the out of scope store.  Furthermore, if there is
> first a store and then a read, like:
>   if (argc != 12312)
>     {
>       char my_char;
>       ptr = &my_char;
>     }
>   *ptr = i + 26;
>   return *ptr;
> we don't notice even the read.  Not sure what could be done against that
> though.  I think we'd need to hook into the into-ssa framework, there it
> should know the current value of the variable at the point of the store is
> result of ASAN_POISON and be able to instead of turning that
>   my_char = _23;
> into
>   my_char_35 = _23;
> turn it into:
>   my_char_35 = ASAN_POISON (_23);
> which would represent after scope store into my_char.
> 
> Not really familiar with into-ssa though to know where to do it.
> 
> 	Jakub
> 

Richi, may I ask you for help with this question?

Thanks,
Martin
Richard Biener Dec. 2, 2016, 12:29 p.m. UTC | #4
On Thu, Dec 1, 2016 at 5:30 PM, Martin Liška <mliska@suse.cz> wrote:
> On 11/23/2016 03:13 PM, Jakub Jelinek wrote:
>> On Wed, Nov 23, 2016 at 02:57:07PM +0100, Martin Liška wrote:
>>> I started review process in libsanitizer: https://reviews.llvm.org/D26965
>>> And I have a question that was asked in the review: can we distinguish between load and store
>>> in case of having usage of ASAN_POISON?
>>
>> I think with ASAN_POISON it is indeed just loads from after scope that can
>> be caught, a store overwrites the variable with a new value and when turning
>> the store after we make the var no longer addressable into SSA form, we
>> loose information about the out of scope store.  Furthermore, if there is
>> first a store and then a read, like:
>>   if (argc != 12312)
>>     {
>>       char my_char;
>>       ptr = &my_char;
>>     }
>>   *ptr = i + 26;
>>   return *ptr;
>> we don't notice even the read.  Not sure what could be done against that
>> though.  I think we'd need to hook into the into-ssa framework, there it
>> should know the current value of the variable at the point of the store is
>> result of ASAN_POISON and be able to instead of turning that
>>   my_char = _23;
>> into
>>   my_char_35 = _23;
>> turn it into:
>>   my_char_35 = ASAN_POISON (_23);
>> which would represent after scope store into my_char.
>>
>> Not really familiar with into-ssa though to know where to do it.
>>
>>       Jakub
>>
>
> Richi, may I ask you for help with this question?

Probably where we handle the CLOBBER case (rewrite_stmt, maybe_register_def),
we do this for -Wuninitialized.

Richard.

> Thanks,
> Martin
>
Martin Liška Dec. 8, 2016, 12:51 p.m. UTC | #5
On 12/02/2016 01:29 PM, Richard Biener wrote:
> On Thu, Dec 1, 2016 at 5:30 PM, Martin Liška <mliska@suse.cz> wrote:
>> On 11/23/2016 03:13 PM, Jakub Jelinek wrote:
>>> On Wed, Nov 23, 2016 at 02:57:07PM +0100, Martin Liška wrote:
>>>> I started review process in libsanitizer: https://reviews.llvm.org/D26965
>>>> And I have a question that was asked in the review: can we distinguish between load and store
>>>> in case of having usage of ASAN_POISON?
>>>
>>> I think with ASAN_POISON it is indeed just loads from after scope that can
>>> be caught, a store overwrites the variable with a new value and when turning
>>> the store after we make the var no longer addressable into SSA form, we
>>> loose information about the out of scope store.  Furthermore, if there is
>>> first a store and then a read, like:
>>>   if (argc != 12312)
>>>     {
>>>       char my_char;
>>>       ptr = &my_char;
>>>     }
>>>   *ptr = i + 26;
>>>   return *ptr;
>>> we don't notice even the read.  Not sure what could be done against that
>>> though.  I think we'd need to hook into the into-ssa framework, there it
>>> should know the current value of the variable at the point of the store is
>>> result of ASAN_POISON and be able to instead of turning that
>>>   my_char = _23;
>>> into
>>>   my_char_35 = _23;
>>> turn it into:
>>>   my_char_35 = ASAN_POISON (_23);
>>> which would represent after scope store into my_char.
>>>
>>> Not really familiar with into-ssa though to know where to do it.
>>>
>>>       Jakub
>>>
>>
>> Richi, may I ask you for help with this question?
> 
> Probably where we handle the CLOBBER case (rewrite_stmt, maybe_register_def),
> we do this for -Wuninitialized.
> 
> Richard.

Thanks for the tip, however as the optimization of memory address store + load happens
before we rewrite my_char into SSA, it would be probably hard to guess which memory
stores and loads should be preserved:

use-after-scope-20.c.032t.ccp1:
main (int argc, char * * argv)
{
  int my_char;
  int * ptr;
  int _1;
  int _11;

  <bb 2> [0.0%]:
  if (argc_4(D) != 12312)
    goto <bb 3>; [0.0%]
  else
    goto <bb 4>; [0.0%]

  <bb 3> [0.0%]:
  ASAN_MARK (2, &my_char, 4);
  ptr_8 = &my_char;
  ASAN_MARK (1, &my_char, 4);

  <bb 4> [0.0%]:
  # ptr_2 = PHI <ptr_5(D)(2), ptr_8(3)>
  _1 = argc_4(D) + 26;
  *ptr_2 = _1;
  _11 = *ptr_2;
  return _11;

}

I sent updated version of patch to LLVM phabricator:
https://reviews.llvm.org/D26965

Hopefully we can cherry pick the patch very soon to our trunk.

M.

> 
>> Thanks,
>> Martin
>>
Richard Biener Dec. 13, 2016, 2:15 p.m. UTC | #6
On Thu, Dec 8, 2016 at 1:51 PM, Martin Liška <mliska@suse.cz> wrote:
> On 12/02/2016 01:29 PM, Richard Biener wrote:
>> On Thu, Dec 1, 2016 at 5:30 PM, Martin Liška <mliska@suse.cz> wrote:
>>> On 11/23/2016 03:13 PM, Jakub Jelinek wrote:
>>>> On Wed, Nov 23, 2016 at 02:57:07PM +0100, Martin Liška wrote:
>>>>> I started review process in libsanitizer: https://reviews.llvm.org/D26965
>>>>> And I have a question that was asked in the review: can we distinguish between load and store
>>>>> in case of having usage of ASAN_POISON?
>>>>
>>>> I think with ASAN_POISON it is indeed just loads from after scope that can
>>>> be caught, a store overwrites the variable with a new value and when turning
>>>> the store after we make the var no longer addressable into SSA form, we
>>>> loose information about the out of scope store.  Furthermore, if there is
>>>> first a store and then a read, like:
>>>>   if (argc != 12312)
>>>>     {
>>>>       char my_char;
>>>>       ptr = &my_char;
>>>>     }
>>>>   *ptr = i + 26;
>>>>   return *ptr;
>>>> we don't notice even the read.  Not sure what could be done against that
>>>> though.  I think we'd need to hook into the into-ssa framework, there it
>>>> should know the current value of the variable at the point of the store is
>>>> result of ASAN_POISON and be able to instead of turning that
>>>>   my_char = _23;
>>>> into
>>>>   my_char_35 = _23;
>>>> turn it into:
>>>>   my_char_35 = ASAN_POISON (_23);
>>>> which would represent after scope store into my_char.
>>>>
>>>> Not really familiar with into-ssa though to know where to do it.
>>>>
>>>>       Jakub
>>>>
>>>
>>> Richi, may I ask you for help with this question?
>>
>> Probably where we handle the CLOBBER case (rewrite_stmt, maybe_register_def),
>> we do this for -Wuninitialized.
>>
>> Richard.
>
> Thanks for the tip, however as the optimization of memory address store + load happens
> before we rewrite my_char into SSA, it would be probably hard to guess which memory
> stores and loads should be preserved:
>
> use-after-scope-20.c.032t.ccp1:
> main (int argc, char * * argv)
> {
>   int my_char;
>   int * ptr;
>   int _1;
>   int _11;
>
>   <bb 2> [0.0%]:
>   if (argc_4(D) != 12312)
>     goto <bb 3>; [0.0%]
>   else
>     goto <bb 4>; [0.0%]
>
>   <bb 3> [0.0%]:
>   ASAN_MARK (2, &my_char, 4);
>   ptr_8 = &my_char;
>   ASAN_MARK (1, &my_char, 4);
>
>   <bb 4> [0.0%]:
>   # ptr_2 = PHI <ptr_5(D)(2), ptr_8(3)>
>   _1 = argc_4(D) + 26;
>   *ptr_2 = _1;
>   _11 = *ptr_2;
>   return _11;
>
> }

The SSA renamer sees

   my_char = ASAN_MARK;
   ptr_8 = &my_char;
   my_char = ASAN_MARK;

?

It does perform a DOM walk when updating the stmts so simply registering the
appropriate current def should do the trick?

> I sent updated version of patch to LLVM phabricator:
> https://reviews.llvm.org/D26965
>
> Hopefully we can cherry pick the patch very soon to our trunk.
>
> M.
>
>>
>>> Thanks,
>>> Martin
>>>
>
diff mbox

Patch

From da190584d091eaaa509067918de4f1f77e887484 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Mon, 14 Nov 2016 16:49:05 +0100
Subject: [PATCH] use-after-scope: introduce ASAN_POISON internal fn

gcc/ChangeLog:

2016-11-16  Martin Liska  <mliska@suse.cz>

	* asan.c (asan_expand_poison_ifn): New function.
	* asan.h (asan_expand_poison_ifn): Declare the function.
	* internal-fn.c (expand_ASAN_POISON): New function.
	* internal-fn.def (ASAN_POISON): New internal fn.
	* sanitizer.def (BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE_NOABORT):
	New built-in.
	(BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE): Likewise.
	* sanopt.c (pass_sanopt::execute): Expand IFN_ASAN_POISON.
	* tree-ssa.c (is_asan_mark_p): New function.
	(execute_update_addresses_taken): Make local variables as not
	addressable if address of these varibles is just taken by
	ASAN_MARK.

gcc/testsuite/ChangeLog:

2016-11-16  Martin Liska  <mliska@suse.cz>

	* gcc.dg/asan/use-after-scope-3.c: Run just with -O0.
	* gcc.dg/asan/use-after-scope-9.c: Run just with -O2 and
	change expected output.
---
 gcc/asan.c                                    | 77 ++++++++++++++++++++++++++-
 gcc/asan.h                                    |  1 +
 gcc/internal-fn.c                             |  7 +++
 gcc/internal-fn.def                           |  1 +
 gcc/sanitizer.def                             |  8 +++
 gcc/sanopt.c                                  |  9 ++++
 gcc/testsuite/gcc.dg/asan/use-after-scope-3.c |  1 +
 gcc/testsuite/gcc.dg/asan/use-after-scope-9.c |  6 +--
 gcc/tree-ssa.c                                | 71 ++++++++++++++++++++----
 libsanitizer/asan/asan_errors.cc              | 21 ++++++++
 libsanitizer/asan/asan_errors.h               | 19 +++++++
 libsanitizer/asan/asan_report.cc              | 10 ++++
 libsanitizer/asan/asan_report.h               |  3 ++
 libsanitizer/asan/asan_rtl.cc                 | 16 ++++++
 14 files changed, 236 insertions(+), 14 deletions(-)

diff --git a/gcc/asan.c b/gcc/asan.c
index 6e93ea3..5bf8052 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -32,8 +32,8 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-pass.h"
 #include "memmodel.h"
 #include "tm_p.h"
+#include "ssa.h"
 #include "stringpool.h"
-#include "tree-vrp.h"
 #include "tree-ssanames.h"
 #include "optabs.h"
 #include "emit-rtl.h"
@@ -2979,6 +2979,81 @@  asan_expand_check_ifn (gimple_stmt_iterator *iter, bool use_calls)
   return true;
 }
 
+
+/* Expand the ASAN_POISON builtins.  */
+
+bool
+asan_expand_poison_ifn (gimple_stmt_iterator *iter,
+			bool *need_commit_edge_insert)
+{
+  gimple *g = gsi_stmt (*iter);
+  tree poisoned_var = gimple_call_lhs (g);
+  if (!poisoned_var)
+    {
+      gsi_remove (iter, true);
+      return true;
+    }
+
+  tree var_decl = SSA_NAME_VAR (poisoned_var);
+
+  bool recover_p;
+  if (flag_sanitize & SANITIZE_USER_ADDRESS)
+    recover_p = (flag_sanitize_recover & SANITIZE_USER_ADDRESS) != 0;
+  else
+    recover_p = (flag_sanitize_recover & SANITIZE_KERNEL_ADDRESS) != 0;
+
+  use_operand_p use_p;
+  imm_use_iterator imm_iter;
+  FOR_EACH_IMM_USE_FAST (use_p, imm_iter, poisoned_var)
+    {
+      gimple *use = USE_STMT (use_p);
+      if (is_gimple_debug (use))
+	continue;
+
+      built_in_function b = (recover_p
+			     ? BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE_NOABORT
+			     : BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE);
+      tree fun = builtin_decl_implicit (b);
+      pretty_printer pp;
+      pp_tree_identifier (&pp, DECL_NAME (var_decl));
+
+      gcall *call = gimple_build_call (fun, 2, asan_pp_string (&pp),
+				       DECL_SIZE_UNIT (var_decl));
+      gimple_set_location (call, gimple_location (use));
+      gimple *call_to_insert = call;
+
+      /* The USE can be a gimple PHI node.  If so, insert the call on
+	 all edges leading to the PHI node.  */
+      if (is_a <gphi *> (use))
+	{
+	  gphi *phi = dyn_cast<gphi *> (use);
+	  for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i)
+	    if (gimple_phi_arg_def (phi, i) == poisoned_var)
+	      {
+		edge e = gimple_phi_arg_edge (phi, i);
+
+		if (call_to_insert == NULL)
+		  call_to_insert = gimple_copy (call);
+
+		gsi_insert_seq_on_edge (e, call_to_insert);
+		*need_commit_edge_insert = true;
+		call_to_insert = NULL;
+	      }
+	}
+      else
+	{
+	  gimple_stmt_iterator gsi = gsi_for_stmt (use);
+	  gsi_insert_before (&gsi, call, GSI_NEW_STMT);
+	}
+    }
+
+  SSA_NAME_IS_DEFAULT_DEF (poisoned_var) = true;
+  SSA_NAME_DEF_STMT (poisoned_var) = gimple_build_nop ();
+  gsi_remove (iter, true);
+
+  return false;
+}
+
 /* Instrument the current function.  */
 
 static unsigned int
diff --git a/gcc/asan.h b/gcc/asan.h
index 9cf5904..6c25955 100644
--- a/gcc/asan.h
+++ b/gcc/asan.h
@@ -30,6 +30,7 @@  extern void initialize_sanitizer_builtins (void);
 extern tree asan_dynamic_init_call (bool);
 extern bool asan_expand_check_ifn (gimple_stmt_iterator *, bool);
 extern bool asan_expand_mark_ifn (gimple_stmt_iterator *);
+extern bool asan_expand_poison_ifn (gimple_stmt_iterator *, bool *);
 
 extern gimple_stmt_iterator create_cond_insert_point
      (gimple_stmt_iterator *, bool, bool, bool, basic_block *, basic_block *);
diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c
index ca347c5..17624e8 100644
--- a/gcc/internal-fn.c
+++ b/gcc/internal-fn.c
@@ -246,6 +246,13 @@  expand_ASAN_MARK (internal_fn, gcall *)
   gcc_unreachable ();
 }
 
+/* This should get expanded in the sanopt pass.  */
+
+static void
+expand_ASAN_POISON (internal_fn, gcall *)
+{
+  gcc_unreachable ();
+}
 
 /* This should get expanded in the tsan pass.  */
 
diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def
index d1cd1a5..9454afd 100644
--- a/gcc/internal-fn.def
+++ b/gcc/internal-fn.def
@@ -159,6 +159,7 @@  DEF_INTERNAL_FN (ABNORMAL_DISPATCHER, ECF_NORETURN, NULL)
 DEF_INTERNAL_FN (BUILTIN_EXPECT, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (ASAN_CHECK, ECF_TM_PURE | ECF_LEAF | ECF_NOTHROW, ".R...")
 DEF_INTERNAL_FN (ASAN_MARK, ECF_LEAF | ECF_NOTHROW, ".R..")
+DEF_INTERNAL_FN (ASAN_POISON, ECF_LEAF | ECF_NOTHROW | ECF_NOVOPS, NULL)
 DEF_INTERNAL_FN (ADD_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (SUB_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (MUL_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 3db08a7..068c55b 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -102,6 +102,14 @@  DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_STORE_N_NOABORT,
 		      "__asan_report_store_n_noabort",
 		      BT_FN_VOID_PTR_PTRMODE,
 		      ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE,
+		      "__asan_report_use_after_scope",
+		      BT_FN_VOID_PTR_PTRMODE,
+		      ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_USE_AFTER_SCOPE_NOABORT,
+		      "__asan_report_use_after_scope_noabort",
+		      BT_FN_VOID_PTR_PTRMODE,
+		      ATTR_TMPURE_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_LOAD1, "__asan_load1",
 		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_LOAD2, "__asan_load2",
diff --git a/gcc/sanopt.c b/gcc/sanopt.c
index 320e14e..77307d9 100644
--- a/gcc/sanopt.c
+++ b/gcc/sanopt.c
@@ -698,6 +698,7 @@  pass_sanopt::execute (function *fun)
   bool use_calls = ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD < INT_MAX
     && asan_num_accesses >= ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD;
 
+  bool need_commit_edge_insert = false;
   FOR_EACH_BB_FN (bb, fun)
     {
       gimple_stmt_iterator gsi;
@@ -735,6 +736,10 @@  pass_sanopt::execute (function *fun)
 		case IFN_ASAN_MARK:
 		  no_next = asan_expand_mark_ifn (&gsi);
 		  break;
+		case IFN_ASAN_POISON:
+		  no_next = asan_expand_poison_ifn (&gsi,
+						    &need_commit_edge_insert);
+		  break;
 		default:
 		  break;
 		}
@@ -766,6 +771,10 @@  pass_sanopt::execute (function *fun)
 	    gsi_next (&gsi);
 	}
     }
+
+  if (need_commit_edge_insert)
+    gsi_commit_edge_inserts ();
+
   return 0;
 }
 
diff --git a/gcc/testsuite/gcc.dg/asan/use-after-scope-3.c b/gcc/testsuite/gcc.dg/asan/use-after-scope-3.c
index 9aeed51..8b11bea 100644
--- a/gcc/testsuite/gcc.dg/asan/use-after-scope-3.c
+++ b/gcc/testsuite/gcc.dg/asan/use-after-scope-3.c
@@ -1,5 +1,6 @@ 
 // { dg-do run }
 // { dg-shouldfail "asan" }
+// { dg-additional-options "-O0" }
 
 int
 main (void)
diff --git a/gcc/testsuite/gcc.dg/asan/use-after-scope-9.c b/gcc/testsuite/gcc.dg/asan/use-after-scope-9.c
index 2e30def..10d7fb5 100644
--- a/gcc/testsuite/gcc.dg/asan/use-after-scope-9.c
+++ b/gcc/testsuite/gcc.dg/asan/use-after-scope-9.c
@@ -1,5 +1,6 @@ 
 // { dg-do run }
 // { dg-shouldfail "asan" }
+// { dg-additional-options "-O2" }
 
 int
 main (int argc, char **argv)
@@ -15,6 +16,5 @@  main (int argc, char **argv)
   return *ptr;
 }
 
-// { dg-output "ERROR: AddressSanitizer: stack-use-after-scope on address.*(\n|\r\n|\r)" }
-// { dg-output "READ of size .*" }
-// { dg-output ".*'a' <== Memory access at offset \[0-9\]* is inside this variable.*" }
+// { dg-output "ERROR: AddressSanitizer: stack-use-after-scope at pc.*(\n|\r\n|\r)" }
+// { dg-output "ACCESS of size .* for variable 'a'" }
diff --git a/gcc/tree-ssa.c b/gcc/tree-ssa.c
index 62eea8b..2d9c62d 100644
--- a/gcc/tree-ssa.c
+++ b/gcc/tree-ssa.c
@@ -41,6 +41,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "cfgexpand.h"
 #include "tree-cfg.h"
 #include "tree-dfa.h"
+#include "asan.h"
 
 /* Pointer map of variable mappings, keyed by edge.  */
 static hash_map<edge, auto_vec<edge_var_map> > *edge_var_maps;
@@ -1550,6 +1551,30 @@  maybe_optimize_var (tree var, bitmap addresses_taken, bitmap not_reg_needs,
     }
 }
 
+/* Return true when STMT is ASAN mark where second argument is an address
+   of a local variable.  */
+
+static bool
+is_asan_mark_p (gimple *stmt)
+{
+  if (!gimple_call_internal_p (stmt, IFN_ASAN_MARK))
+    return false;
+
+  tree addr = get_base_address (gimple_call_arg (stmt, 1));
+  if (TREE_CODE (addr) == ADDR_EXPR
+      && VAR_P (TREE_OPERAND (addr, 0)))
+    {
+      tree var = TREE_OPERAND (addr, 0);
+      unsigned addressable = TREE_ADDRESSABLE (var);
+      TREE_ADDRESSABLE (var) = 0;
+      bool r = is_gimple_reg (var);
+      TREE_ADDRESSABLE (var) = addressable;
+      return r;
+    }
+
+  return false;
+}
+
 /* Compute TREE_ADDRESSABLE and DECL_GIMPLE_REG_P for local variables.  */
 
 void
@@ -1575,17 +1600,23 @@  execute_update_addresses_taken (void)
 	  enum gimple_code code = gimple_code (stmt);
 	  tree decl;
 
-	  if (code == GIMPLE_CALL
-	      && optimize_atomic_compare_exchange_p (stmt))
+	  if (code == GIMPLE_CALL)
 	    {
-	      /* For __atomic_compare_exchange_N if the second argument
-		 is &var, don't mark var addressable;
-		 if it becomes non-addressable, we'll rewrite it into
-		 ATOMIC_COMPARE_EXCHANGE call.  */
-	      tree arg = gimple_call_arg (stmt, 1);
-	      gimple_call_set_arg (stmt, 1, null_pointer_node);
-	      gimple_ior_addresses_taken (addresses_taken, stmt);
-	      gimple_call_set_arg (stmt, 1, arg);
+	      if (optimize_atomic_compare_exchange_p (stmt))
+		{
+		  /* For __atomic_compare_exchange_N if the second argument
+		     is &var, don't mark var addressable;
+		     if it becomes non-addressable, we'll rewrite it into
+		     ATOMIC_COMPARE_EXCHANGE call.  */
+		  tree arg = gimple_call_arg (stmt, 1);
+		  gimple_call_set_arg (stmt, 1, null_pointer_node);
+		  gimple_ior_addresses_taken (addresses_taken, stmt);
+		  gimple_call_set_arg (stmt, 1, arg);
+		}
+	      else if (is_asan_mark_p (stmt))
+		;
+	      else
+		gimple_ior_addresses_taken (addresses_taken, stmt);
 	    }
 	  else
 	    /* Note all addresses taken by the stmt.  */
@@ -1841,6 +1872,26 @@  execute_update_addresses_taken (void)
 			continue;
 		      }
 		  }
+		else if (is_asan_mark_p (stmt))
+		  {
+		    tree var = TREE_OPERAND (gimple_call_arg (stmt, 1), 0);
+		    if (bitmap_bit_p (suitable_for_renaming, DECL_UID (var)))
+		      {
+			HOST_WIDE_INT flags
+			  = tree_to_shwi (gimple_call_arg (stmt, 0));
+			unlink_stmt_vdef (stmt);
+			if (flags & ASAN_MARK_CLOBBER)
+			  {
+			    gcall *call
+			      = gimple_build_call_internal (IFN_ASAN_POISON, 0);
+			    gimple_call_set_lhs (call, var);
+			    gsi_replace (&gsi, call, GSI_SAME_STMT);
+			  }
+			else
+			  gsi_remove (&gsi, true);
+			continue;
+		      }
+		  }
 		for (i = 0; i < gimple_call_num_args (stmt); ++i)
 		  {
 		    tree *argp = gimple_call_arg_ptr (stmt, i);
diff --git a/libsanitizer/asan/asan_errors.cc b/libsanitizer/asan/asan_errors.cc
index 73c4cca..f17c9b7 100644
--- a/libsanitizer/asan/asan_errors.cc
+++ b/libsanitizer/asan/asan_errors.cc
@@ -279,6 +279,27 @@  void ErrorInvalidPointerPair::Print() {
   ReportErrorSummary(bug_type, &stack);
 }
 
+void ErrorUseAfterScope::Print() {
+  const char *bug_type = "stack-use-after-scope";
+  Decorator d;
+  Printf("%s", d.Warning());
+
+  Report("ERROR: AddressSanitizer: stack-use-after-scope at pc %p bp %p sp %p\n",
+         variable_name, variable_size, pc, bp, sp);
+  Printf("%s", d.EndWarning());
+  scariness.Print();
+
+  char tname[128];
+  Printf("ACCESS of size %zu for variable '%s' thread T%d%s%s\n",
+         variable_size, variable_name, tid,
+         ThreadNameWithParenthesis(tid, tname, sizeof(tname)), d.EndAccess());
+
+  GET_STACK_TRACE_FATAL(pc, bp);
+  stack.Print();
+  ReportErrorSummary(bug_type, &stack);
+}
+
+
 static bool AdjacentShadowValuesAreFullyPoisoned(u8 *s) {
   return s[-1] > 127 && s[1] > 127;
 }
diff --git a/libsanitizer/asan/asan_errors.h b/libsanitizer/asan/asan_errors.h
index 6262dcf..a843859 100644
--- a/libsanitizer/asan/asan_errors.h
+++ b/libsanitizer/asan/asan_errors.h
@@ -294,6 +294,24 @@  struct ErrorInvalidPointerPair : ErrorBase {
   void Print();
 };
 
+struct ErrorUseAfterScope : ErrorBase {
+  uptr pc, bp, sp;
+  const char *variable_name;
+  uptr variable_size;
+  // VS2013 doesn't implement unrestricted unions, so we need a trivial default
+  // constructor
+  ErrorUseAfterScope() = default;
+  ErrorUseAfterScope(u32 tid, uptr pc_, uptr bp_, uptr sp_,
+                     const char *variable_name_, uptr variable_size_)
+      : ErrorBase(tid),
+        pc(pc_),
+        bp(bp_),
+        sp(sp_),
+	variable_name(variable_name_),
+	variable_size(variable_size_) {}
+  void Print();
+};
+
 struct ErrorGeneric : ErrorBase {
   AddressDescription addr_description;
   uptr pc, bp, sp;
@@ -324,6 +342,7 @@  struct ErrorGeneric : ErrorBase {
   macro(BadParamsToAnnotateContiguousContainer) \
   macro(ODRViolation)                           \
   macro(InvalidPointerPair)                     \
+  macro(UseAfterScope)                          \
   macro(Generic)
 // clang-format on
 
diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc
index 84d6764..db39d42 100644
--- a/libsanitizer/asan/asan_report.cc
+++ b/libsanitizer/asan/asan_report.cc
@@ -353,6 +353,16 @@  static INLINE void CheckForInvalidPointerPair(void *p1, void *p2) {
     return ReportInvalidPointerPair(pc, bp, sp, a1, a2);
   }
 }
+// ----------------------- ReportUseAfterScope ----------- {{{1
+void ReportUseAfterScope(const char *variable_name, uptr variable_size,
+                         bool fatal) {
+  ScopedInErrorReport in_report (fatal);
+  GET_CALLER_PC_BP_SP;
+  ErrorUseAfterScope error(GetCurrentTidOrInvalid(), pc, bp, sp, variable_name,
+                           variable_size);
+  in_report.ReportError(error);
+}
+
 // ----------------------- Mac-specific reports ----------------- {{{1
 
 void ReportMacMzReallocUnknown(uptr addr, uptr zone_ptr, const char *zone_name,
diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h
index 111b840..2fa158a 100644
--- a/libsanitizer/asan/asan_report.h
+++ b/libsanitizer/asan/asan_report.h
@@ -68,6 +68,9 @@  void ReportBadParamsToAnnotateContiguousContainer(uptr beg, uptr end,
 void ReportODRViolation(const __asan_global *g1, u32 stack_id1,
                         const __asan_global *g2, u32 stack_id2);
 
+void ReportUseAfterScope(const char *variable_name, uptr variable_size,
+                         bool fatal);
+
 // Mac-specific errors and warnings.
 void ReportMacMzReallocUnknown(uptr addr, uptr zone_ptr,
                                const char *zone_name,
diff --git a/libsanitizer/asan/asan_rtl.cc b/libsanitizer/asan/asan_rtl.cc
index 38009d2..f637d71 100644
--- a/libsanitizer/asan/asan_rtl.cc
+++ b/libsanitizer/asan/asan_rtl.cc
@@ -253,6 +253,22 @@  void __asan_storeN_noabort(uptr addr, uptr size) {
   }
 }
 
+#include <stdio.h>
+
+extern "C"
+NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_use_after_scope(const char *variable_name,
+                                   uptr variable_size) {
+  ReportUseAfterScope(variable_name, variable_size, true);
+}
+
+extern "C"
+NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_use_after_scope_noabort(const char *variable_name,
+                                           uptr variable_size) {
+  ReportUseAfterScope(variable_name, variable_size, false);
+}
+
 // Force the linker to keep the symbols for various ASan interface functions.
 // We want to keep those in the executable in order to let the instrumented
 // dynamic libraries access the symbol even if it is not used by the executable
-- 
2.10.2