Fix exception reporting from interpreter

To comply with JDWP exception report rules, we must report an exception at the
location of the throw (or the first instruction encountered after a native
call). To do this, we use the CatchLocationFinder visitor to look for a catch
handler until we reach a native frame or the top frame.

Because interpreter handles pending exception on a method-by-method basis, we
need a flag to remember we already reported the exception and avoid reporting
it multiple times when unwinding methods. The drawback is we need to maintain
the state of this flag. We clear it when the exception is cleared. In the case
we temporarily clear the exception (when finding a catch handler for instance),
we restore the flag to its previous value at the same time we restore the
pending exception.

Bump oat version to force recompilation because we modify Thread offsets.

Bug: 14402770
Change-Id: Ic059c58f80b2023b118038301f8f0a24f1e18241
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 73ed590..a0cecb0 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -3430,6 +3430,7 @@
   auto old_throw_method = hs.NewHandle<mirror::ArtMethod>(nullptr);
   auto old_exception = hs.NewHandle<mirror::Throwable>(nullptr);
   uint32_t old_throw_dex_pc;
+  bool old_exception_report_flag;
   {
     ThrowLocation old_throw_location;
     mirror::Throwable* old_exception_obj = soa.Self()->GetException(&old_throw_location);
@@ -3437,6 +3438,7 @@
     old_throw_method.Assign(old_throw_location.GetMethod());
     old_exception.Assign(old_exception_obj);
     old_throw_dex_pc = old_throw_location.GetDexPc();
+    old_exception_report_flag = soa.Self()->IsExceptionReportedToInstrumentation();
     soa.Self()->ClearException();
   }
 
@@ -3491,6 +3493,7 @@
     ThrowLocation gc_safe_throw_location(old_throw_this_object.Get(), old_throw_method.Get(),
                                          old_throw_dex_pc);
     soa.Self()->SetException(gc_safe_throw_location, old_exception.Get());
+    soa.Self()->SetExceptionReportedToInstrumentation(old_exception_report_flag);
   }
 }
 
diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h
index 09899c0..3d8b29f 100644
--- a/runtime/entrypoints/entrypoint_utils.h
+++ b/runtime/entrypoints/entrypoint_utils.h
@@ -652,6 +652,7 @@
   // Save any pending exception over monitor exit call.
   mirror::Throwable* saved_exception = NULL;
   ThrowLocation saved_throw_location;
+  bool is_exception_reported = self->IsExceptionReportedToInstrumentation();
   if (UNLIKELY(self->IsExceptionPending())) {
     saved_exception = self->GetException(&saved_throw_location);
     self->ClearException();
@@ -667,6 +668,7 @@
   // Restore pending exception.
   if (saved_exception != NULL) {
     self->SetException(saved_throw_location, saved_exception);
+    self->SetExceptionReportedToInstrumentation(is_exception_reported);
   }
 }
 
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 261c241..8f5da83 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -780,24 +780,20 @@
 void Instrumentation::FieldReadEventImpl(Thread* thread, mirror::Object* this_object,
                                          mirror::ArtMethod* method, uint32_t dex_pc,
                                          mirror::ArtField* field) const {
-  if (have_field_read_listeners_) {
-    // TODO: same comment than DexPcMovedEventImpl.
-    std::list<InstrumentationListener*> copy(field_read_listeners_);
-    for (InstrumentationListener* listener : copy) {
-      listener->FieldRead(thread, this_object, method, dex_pc, field);
-    }
+  // TODO: same comment than DexPcMovedEventImpl.
+  std::list<InstrumentationListener*> copy(field_read_listeners_);
+  for (InstrumentationListener* listener : copy) {
+    listener->FieldRead(thread, this_object, method, dex_pc, field);
   }
 }
 
 void Instrumentation::FieldWriteEventImpl(Thread* thread, mirror::Object* this_object,
                                          mirror::ArtMethod* method, uint32_t dex_pc,
                                          mirror::ArtField* field, const JValue& field_value) const {
-  if (have_field_write_listeners_) {
-    // TODO: same comment than DexPcMovedEventImpl.
-    std::list<InstrumentationListener*> copy(field_write_listeners_);
-    for (InstrumentationListener* listener : copy) {
-      listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value);
-    }
+  // TODO: same comment than DexPcMovedEventImpl.
+  std::list<InstrumentationListener*> copy(field_write_listeners_);
+  for (InstrumentationListener* listener : copy) {
+    listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value);
   }
 }
 
@@ -805,8 +801,9 @@
                                            mirror::ArtMethod* catch_method,
                                            uint32_t catch_dex_pc,
                                            mirror::Throwable* exception_object) const {
-  if (have_exception_caught_listeners_) {
-    DCHECK_EQ(thread->GetException(NULL), exception_object);
+  if (HasExceptionCaughtListeners()) {
+    DCHECK_EQ(thread->GetException(nullptr), exception_object);
+    bool is_exception_reported = thread->IsExceptionReportedToInstrumentation();
     thread->ClearException();
     // TODO: The copy below is due to the debug listener having an action where it can remove
     // itself as a listener and break the iterator. The copy only works around the problem.
@@ -815,6 +812,7 @@
       listener->ExceptionCaught(thread, throw_location, catch_method, catch_dex_pc, exception_object);
     }
     thread->SetException(throw_location, exception_object);
+    thread->SetExceptionReportedToInstrumentation(is_exception_reported);
   }
 }
 
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 6625801..d0cb4de 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -237,6 +237,10 @@
     return have_field_write_listeners_;
   }
 
+  bool HasExceptionCaughtListeners() const {
+    return have_exception_caught_listeners_;
+  }
+
   bool IsActive() const {
     return have_dex_pc_listeners_ || have_method_entry_listeners_ || have_method_exit_listeners_ ||
         have_field_read_listeners_ || have_field_write_listeners_ ||
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index e1fe563..c7fb884 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -372,31 +372,107 @@
 #undef EXPLICIT_DO_IPUT_QUICK_ALL_TEMPLATE_DECL
 #undef EXPLICIT_DO_IPUT_QUICK_TEMPLATE_DECL
 
+/**
+ * Finds the location where this exception will be caught. We search until we reach either the top
+ * frame or a native frame, in which cases this exception is considered uncaught.
+ */
+class CatchLocationFinder : public StackVisitor {
+ public:
+  explicit CatchLocationFinder(Thread* self, Handle<mirror::Throwable>* exception)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
+    : StackVisitor(self, nullptr), self_(self), handle_scope_(self), exception_(exception),
+      catch_method_(handle_scope_.NewHandle<mirror::ArtMethod>(nullptr)),
+      catch_dex_pc_(DexFile::kDexNoIndex), clear_exception_(false) {
+  }
+
+  bool VisitFrame() OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::ArtMethod* method = GetMethod();
+    if (method == nullptr) {
+      return true;
+    }
+    if (method->IsRuntimeMethod()) {
+      // Ignore callee save method.
+      DCHECK(method->IsCalleeSaveMethod());
+      return true;
+    }
+    if (method->IsNative()) {
+      return false;  // End stack walk.
+    }
+    DCHECK(!method->IsNative());
+    uint32_t dex_pc = GetDexPc();
+    if (dex_pc != DexFile::kDexNoIndex) {
+      uint32_t found_dex_pc;
+      {
+        StackHandleScope<3> hs(self_);
+        Handle<mirror::Class> exception_class(hs.NewHandle((*exception_)->GetClass()));
+        Handle<mirror::ArtMethod> h_method(hs.NewHandle(method));
+        found_dex_pc = mirror::ArtMethod::FindCatchBlock(h_method, exception_class, dex_pc,
+                                                         &clear_exception_);
+      }
+      if (found_dex_pc != DexFile::kDexNoIndex) {
+        catch_method_.Assign(method);
+        catch_dex_pc_ = found_dex_pc;
+        return false;  // End stack walk.
+      }
+    }
+    return true;  // Continue stack walk.
+  }
+
+  ArtMethod* GetCatchMethod() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    return catch_method_.Get();
+  }
+
+  uint32_t GetCatchDexPc() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    return catch_dex_pc_;
+  }
+
+  bool NeedClearException() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    return clear_exception_;
+  }
+
+ private:
+  Thread* const self_;
+  StackHandleScope<1> handle_scope_;
+  Handle<mirror::Throwable>* exception_;
+  Handle<mirror::ArtMethod> catch_method_;
+  uint32_t catch_dex_pc_;
+  bool clear_exception_;
+
+
+  DISALLOW_COPY_AND_ASSIGN(CatchLocationFinder);
+};
+
 uint32_t FindNextInstructionFollowingException(Thread* self,
                                                ShadowFrame& shadow_frame,
                                                uint32_t dex_pc,
-                                               mirror::Object* this_object,
                                                const instrumentation::Instrumentation* instrumentation) {
   self->VerifyStack();
   ThrowLocation throw_location;
-  mirror::Throwable* exception = self->GetException(&throw_location);
+  StackHandleScope<3> hs(self);
+  Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException(&throw_location)));
+  if (!self->IsExceptionReportedToInstrumentation() && instrumentation->HasExceptionCaughtListeners()) {
+    CatchLocationFinder clf(self, &exception);
+    clf.WalkStack(false);
+    instrumentation->ExceptionCaughtEvent(self, throw_location, clf.GetCatchMethod(),
+                                          clf.GetCatchDexPc(), exception.Get());
+    self->SetExceptionReportedToInstrumentation(true);
+  }
   bool clear_exception = false;
   uint32_t found_dex_pc;
   {
-    StackHandleScope<3> hs(self);
     Handle<mirror::Class> exception_class(hs.NewHandle(exception->GetClass()));
     Handle<mirror::ArtMethod> h_method(hs.NewHandle(shadow_frame.GetMethod()));
-    HandleWrapper<mirror::Object> h(hs.NewHandleWrapper(&this_object));
     found_dex_pc = mirror::ArtMethod::FindCatchBlock(h_method, exception_class, dex_pc,
                                                      &clear_exception);
   }
   if (found_dex_pc == DexFile::kDexNoIndex) {
-    instrumentation->MethodUnwindEvent(self, this_object,
+    instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(),
                                        shadow_frame.GetMethod(), dex_pc);
   } else {
-    instrumentation->ExceptionCaughtEvent(self, throw_location,
-                                          shadow_frame.GetMethod(),
-                                          found_dex_pc, exception);
+    if (self->IsExceptionReportedToInstrumentation()) {
+      instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(),
+                                         shadow_frame.GetMethod(), dex_pc);
+    }
     if (clear_exception) {
       self->ClearException();
     }
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 0c69fe9..d18f9f9 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -334,8 +334,7 @@
 }
 
 uint32_t FindNextInstructionFollowingException(Thread* self, ShadowFrame& shadow_frame,
-    uint32_t dex_pc, mirror::Object* this_object,
-    const instrumentation::Instrumentation* instrumentation)
+    uint32_t dex_pc, const instrumentation::Instrumentation* instrumentation)
         SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
 void UnexpectedOpcode(const Instruction* inst, MethodHelper& mh)
diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc
index 19673ac..cb4868c 100644
--- a/runtime/interpreter/interpreter_goto_table_impl.cc
+++ b/runtime/interpreter/interpreter_goto_table_impl.cc
@@ -2391,10 +2391,8 @@
       CheckSuspend(self);
       UPDATE_HANDLER_TABLE();
     }
-    Object* this_object = shadow_frame.GetThisObject(code_item->ins_size_);
     instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
     uint32_t found_dex_pc = FindNextInstructionFollowingException(self, shadow_frame, dex_pc,
-                                                                  this_object,
                                                                   instrumentation);
     if (found_dex_pc == DexFile::kDexNoIndex) {
       return JValue(); /* Handled in caller. */
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index a43fad3..bdf2a20 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -25,10 +25,8 @@
     if (UNLIKELY(self->TestAllFlags())) {                                                       \
       CheckSuspend(self);                                                                       \
     }                                                                                           \
-    Object* this_object = shadow_frame.GetThisObject(code_item->ins_size_);                     \
     uint32_t found_dex_pc = FindNextInstructionFollowingException(self, shadow_frame,           \
                                                                   inst->GetDexPc(insns),        \
-                                                                  this_object,                  \
                                                                   instrumentation);             \
     if (found_dex_pc == DexFile::kDexNoIndex) {                                                 \
       return JValue(); /* Handled in caller. */                                                 \
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 19ee1ff..66406bf 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -682,6 +682,7 @@
     auto old_throw_method(hs.NewHandle<mirror::ArtMethod>(nullptr));
     auto old_exception(hs.NewHandle<mirror::Throwable>(nullptr));
     uint32_t old_throw_dex_pc;
+    bool old_is_exception_reported;
     {
       ThrowLocation old_throw_location;
       mirror::Throwable* old_exception_obj = soa.Self()->GetException(&old_throw_location);
@@ -689,6 +690,7 @@
       old_throw_method.Assign(old_throw_location.GetMethod());
       old_exception.Assign(old_exception_obj);
       old_throw_dex_pc = old_throw_location.GetDexPc();
+      old_is_exception_reported = soa.Self()->IsExceptionReportedToInstrumentation();
       soa.Self()->ClearException();
     }
     ScopedLocalRef<jthrowable> exception(env,
@@ -710,6 +712,7 @@
                                          old_throw_dex_pc);
 
     soa.Self()->SetException(gc_safe_throw_location, old_exception.Get());
+    soa.Self()->SetExceptionReportedToInstrumentation(old_is_exception_reported);
   }
 
   static jthrowable ExceptionOccurred(JNIEnv* env) {
diff --git a/runtime/mirror/art_method.cc b/runtime/mirror/art_method.cc
index 3db4be3..4821e29 100644
--- a/runtime/mirror/art_method.cc
+++ b/runtime/mirror/art_method.cc
@@ -240,6 +240,7 @@
   ThrowLocation throw_location;
   StackHandleScope<1> hs(self);
   Handle<mirror::Throwable> exception(hs.NewHandle(self->GetException(&throw_location)));
+  bool is_exception_reported = self->IsExceptionReportedToInstrumentation();
   self->ClearException();
   // Default to handler not found.
   uint32_t found_dex_pc = DexFile::kDexNoIndex;
@@ -276,6 +277,7 @@
   // Put the exception back.
   if (exception.Get() != nullptr) {
     self->SetException(throw_location, exception.Get());
+    self->SetExceptionReportedToInstrumentation(is_exception_reported);
   }
   return found_dex_pc;
 }
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 7b31a82..a20f7b9 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -84,7 +84,7 @@
     Handle<mirror::Object> old_throw_this_object(hs.NewHandle(old_throw_location.GetThis()));
     Handle<mirror::ArtMethod> old_throw_method(hs.NewHandle(old_throw_location.GetMethod()));
     uint32_t old_throw_dex_pc = old_throw_location.GetDexPc();
-
+    bool is_exception_reported = self->IsExceptionReportedToInstrumentation();
     // clear exception to call FindSystemClass
     self->ClearException();
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -102,8 +102,8 @@
     // Restore exception.
     ThrowLocation gc_safe_throw_location(old_throw_this_object.Get(), old_throw_method.Get(),
                                          old_throw_dex_pc);
-
     self->SetException(gc_safe_throw_location, old_exception.Get());
+    self->SetExceptionReportedToInstrumentation(is_exception_reported);
   }
   COMPILE_ASSERT(sizeof(Status) == sizeof(uint32_t), size_of_status_not_uint32);
   if (Runtime::Current()->IsActiveTransaction()) {
diff --git a/runtime/oat.cc b/runtime/oat.cc
index ecd1983..f4721f2 100644
--- a/runtime/oat.cc
+++ b/runtime/oat.cc
@@ -22,7 +22,7 @@
 namespace art {
 
 const uint8_t OatHeader::kOatMagic[] = { 'o', 'a', 't', '\n' };
-const uint8_t OatHeader::kOatVersion[] = { '0', '3', '4', '\0' };
+const uint8_t OatHeader::kOatVersion[] = { '0', '3', '5', '\0' };
 
 OatHeader::OatHeader() {
   memset(this, 0, sizeof(*this));
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index e3f9afc..1034923 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -110,7 +110,8 @@
 };
 
 void QuickExceptionHandler::FindCatch(const ThrowLocation& throw_location,
-                                      mirror::Throwable* exception) {
+                                      mirror::Throwable* exception,
+                                      bool is_exception_reported) {
   DCHECK(!is_deoptimization_);
   if (kDebugExceptionDelivery) {
     mirror::String* msg = exception->GetDetailMessage();
@@ -141,12 +142,24 @@
   } else {
     // Put exception back in root set with clear throw location.
     self_->SetException(ThrowLocation(), exception_ref.Get());
+    self_->SetExceptionReportedToInstrumentation(is_exception_reported);
   }
   // The debugger may suspend this thread and walk its stack. Let's do this before popping
   // instrumentation frames.
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  instrumentation->ExceptionCaughtEvent(self_, throw_location, handler_method_, handler_dex_pc_,
-                                        exception_ref.Get());
+  if (!is_exception_reported) {
+    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+    instrumentation->ExceptionCaughtEvent(self_, throw_location, handler_method_, handler_dex_pc_,
+                                          exception_ref.Get());
+      // We're not catching this exception but let's remind we already reported the exception above
+      // to avoid reporting it twice.
+      self_->SetExceptionReportedToInstrumentation(true);
+  }
+  bool caught_exception = (handler_method_ != nullptr && handler_dex_pc_ != DexFile::kDexNoIndex);
+  if (caught_exception) {
+    // We're catching this exception so we finish reporting it. We do it here to avoid doing it
+    // in the compiled code.
+    self_->SetExceptionReportedToInstrumentation(false);
+  }
 }
 
 // Prepares deoptimization.
diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h
index a4229b3..1d600ed 100644
--- a/runtime/quick_exception_handler.h
+++ b/runtime/quick_exception_handler.h
@@ -42,7 +42,8 @@
     LOG(FATAL) << "UNREACHABLE";  // Expected to take long jump.
   }
 
-  void FindCatch(const ThrowLocation& throw_location, mirror::Throwable* exception)
+  void FindCatch(const ThrowLocation& throw_location, mirror::Throwable* exception,
+                 bool is_exception_reported)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   void DeoptimizeStack() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   void UpdateInstrumentationStack() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 7f7b542..021c7c1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1611,6 +1611,7 @@
   Handle<mirror::ArtMethod> saved_throw_method(hs.NewHandle(throw_location.GetMethod()));
   // Ignore the cause throw location. TODO: should we report this as a re-throw?
   ScopedLocalRef<jobject> cause(GetJniEnv(), soa.AddLocalReference<jobject>(GetException(nullptr)));
+  bool is_exception_reported = IsExceptionReportedToInstrumentation();
   ClearException();
   Runtime* runtime = Runtime::Current();
 
@@ -1641,6 +1642,7 @@
     ThrowLocation gc_safe_throw_location(saved_throw_this.Get(), saved_throw_method.Get(),
                                          throw_location.GetDexPc());
     SetException(gc_safe_throw_location, Runtime::Current()->GetPreAllocatedOutOfMemoryError());
+    SetExceptionReportedToInstrumentation(is_exception_reported);
     return;
   }
 
@@ -1693,6 +1695,7 @@
     ThrowLocation gc_safe_throw_location(saved_throw_this.Get(), saved_throw_method.Get(),
                                          throw_location.GetDexPc());
     SetException(gc_safe_throw_location, exception.Get());
+    SetExceptionReportedToInstrumentation(is_exception_reported);
   } else {
     jvalue jv_args[2];
     size_t i = 0;
@@ -1710,6 +1713,7 @@
       ThrowLocation gc_safe_throw_location(saved_throw_this.Get(), saved_throw_method.Get(),
                                            throw_location.GetDexPc());
       SetException(gc_safe_throw_location, exception.Get());
+      SetExceptionReportedToInstrumentation(is_exception_reported);
     }
   }
 }
@@ -1892,13 +1896,14 @@
   CHECK(exception != nullptr);
   // Don't leave exception visible while we try to find the handler, which may cause class
   // resolution.
+  bool is_exception_reported = IsExceptionReportedToInstrumentation();
   ClearException();
   bool is_deoptimization = (exception == GetDeoptimizationException());
   QuickExceptionHandler exception_handler(this, is_deoptimization);
   if (is_deoptimization) {
     exception_handler.DeoptimizeStack();
   } else {
-    exception_handler.FindCatch(throw_location, exception);
+    exception_handler.FindCatch(throw_location, exception, is_exception_reported);
   }
   exception_handler.UpdateInstrumentationStack();
   exception_handler.DoLongJump();
diff --git a/runtime/thread.h b/runtime/thread.h
index 5de54b3..bff9b52 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -329,6 +329,7 @@
   void ClearException() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     tlsPtr_.exception = nullptr;
     tlsPtr_.throw_location.Clear();
+    SetExceptionReportedToInstrumentation(false);
   }
 
   // Find catch block and perform long jump to appropriate exception handle
@@ -809,6 +810,14 @@
     tlsPtr_.rosalloc_runs[index] = run;
   }
 
+  bool IsExceptionReportedToInstrumentation() const {
+    return tls32_.is_exception_reported_to_instrumentation_;
+  }
+
+  void SetExceptionReportedToInstrumentation(bool reported) {
+    tls32_.is_exception_reported_to_instrumentation_ = reported;
+  }
+
  private:
   explicit Thread(bool daemon);
   ~Thread() LOCKS_EXCLUDED(Locks::mutator_lock_,
@@ -911,7 +920,7 @@
     explicit tls_32bit_sized_values(bool is_daemon) :
       suspend_count(0), debug_suspend_count(0), thin_lock_thread_id(0), tid(0),
       daemon(is_daemon), throwing_OutOfMemoryError(false), no_thread_suspension(0),
-      thread_exit_check_count(0) {
+      thread_exit_check_count(0), is_exception_reported_to_instrumentation_(false) {
     }
 
     union StateAndFlags state_and_flags;
@@ -947,6 +956,10 @@
 
     // How many times has our pthread key's destructor been called?
     uint32_t thread_exit_check_count;
+
+    // When true this field indicates that the exception associated with this thread has already
+    // been reported to instrumentation.
+    bool32_t is_exception_reported_to_instrumentation_;
   } tls32_;
 
   struct PACKED(8) tls_64bit_sized_values {