Merge "Fix transaction aborting"
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 7451bd5..2d8c9d4 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1929,7 +1929,7 @@
                 *file_log << exception->Dump() << "\n";
               }
               soa.Self()->ClearException();
-              transaction.Abort();
+              transaction.Rollback();
               CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
             }
           }
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index b66dfeb..e0df6db 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -4220,6 +4220,14 @@
       WrapExceptionInInitializer(klass);
       klass->SetStatus(mirror::Class::kStatusError, self);
       success = false;
+    } else if (Runtime::Current()->IsTransactionAborted()) {
+      // The exception thrown when the transaction aborted has been caught and cleared
+      // so we need to throw it again now.
+      LOG(WARNING) << "Return from class initializer of " << PrettyDescriptor(klass.Get())
+                   << " without exception while transaction was aborted: re-throw it now.";
+      Runtime::Current()->ThrowInternalErrorForAbortedTransaction(self);
+      klass->SetStatus(mirror::Class::kStatusError, self);
+      success = false;
     } else {
       RuntimeStats* global_stats = Runtime::Current()->GetStats();
       RuntimeStats* thread_stats = self->GetStats();
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 2a63456..a29558e 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -546,11 +546,13 @@
 
 void AbortTransaction(Thread* self, const char* fmt, ...) {
   CHECK(Runtime::Current()->IsActiveTransaction());
-  // Throw an exception so we can abort the transaction and undo every change.
+  // Constructs abort message.
   va_list args;
   va_start(args, fmt);
-  self->ThrowNewExceptionV(self->GetCurrentLocationForThrow(), "Ljava/lang/InternalError;", fmt,
-                           args);
+  std::string abort_msg;
+  StringAppendV(&abort_msg, fmt, args);
+  // Throws an exception so we can abort the transaction and rollback every change.
+  Runtime::Current()->AbortTransactionAndThrowInternalError(self, abort_msg);
   va_end(args);
 }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 549ef0e..43f3a2e 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1480,6 +1480,31 @@
   preinitialization_transaction_ = nullptr;
 }
 
+
+bool Runtime::IsTransactionAborted() const {
+  if (!IsActiveTransaction()) {
+    return false;
+  } else {
+    DCHECK(IsCompiler());
+    return preinitialization_transaction_->IsAborted();
+  }
+}
+
+void Runtime::AbortTransactionAndThrowInternalError(Thread* self,
+                                                    const std::string& abort_message) {
+  DCHECK(IsCompiler());
+  DCHECK(IsActiveTransaction());
+  preinitialization_transaction_->Abort(abort_message);
+  ThrowInternalErrorForAbortedTransaction(self);
+}
+
+void Runtime::ThrowInternalErrorForAbortedTransaction(Thread* self) {
+  DCHECK(IsCompiler());
+  DCHECK(IsActiveTransaction());
+  DCHECK(IsTransactionAborted());
+  preinitialization_transaction_->ThrowInternalError(self);
+}
+
 void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
                                       uint8_t value, bool is_volatile) const {
   DCHECK(IsCompiler());
diff --git a/runtime/runtime.h b/runtime/runtime.h
index c5a8739..118c838 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -455,6 +455,13 @@
   }
   void EnterTransactionMode(Transaction* transaction);
   void ExitTransactionMode();
+  bool IsTransactionAborted() const;
+
+  void AbortTransactionAndThrowInternalError(Thread* self, const std::string& abort_message)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void ThrowInternalErrorForAbortedTransaction(Thread* self)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
   void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value,
                                bool is_volatile) const;
   void RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset, int8_t value,
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 118c1a2..7e2e0a6 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -30,7 +30,8 @@
 // TODO: remove (only used for debugging purpose).
 static constexpr bool kEnableTransactionStats = false;
 
-Transaction::Transaction() : log_lock_("transaction log lock", kTransactionLogLock) {
+Transaction::Transaction()
+  : log_lock_("transaction log lock", kTransactionLogLock), aborted_(false) {
   CHECK(Runtime::Current()->IsCompiler());
 }
 
@@ -57,6 +58,35 @@
   }
 }
 
+void Transaction::Abort(const std::string& abort_message) {
+  MutexLock mu(Thread::Current(), log_lock_);
+  // We may abort more than once if the java.lang.InternalError thrown at the
+  // time of the abort has been caught during execution of a class initializer.
+  // We just keep the message of the first abort because it will cause the
+  // transaction to be rolled back anyway.
+  if (!aborted_) {
+    aborted_ = true;
+    abort_message_ = abort_message;
+  }
+}
+
+void Transaction::ThrowInternalError(Thread* self) {
+  DCHECK(IsAborted());
+  std::string abort_msg(GetAbortMessage());
+  self->ThrowNewException(self->GetCurrentLocationForThrow(), "Ljava/lang/InternalError;",
+                          abort_msg.c_str());
+}
+
+bool Transaction::IsAborted() {
+  MutexLock mu(Thread::Current(), log_lock_);
+  return aborted_;
+}
+
+const std::string& Transaction::GetAbortMessage() {
+  MutexLock mu(Thread::Current(), log_lock_);
+  return abort_message_;
+}
+
 void Transaction::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
                                           uint8_t value, bool is_volatile) {
   DCHECK(obj != nullptr);
@@ -150,7 +180,7 @@
   intern_string_logs_.push_front(log);
 }
 
-void Transaction::Abort() {
+void Transaction::Rollback() {
   CHECK(!Runtime::Current()->IsActiveTransaction());
   Thread* self = Thread::Current();
   self->AssertNoPendingException();
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 8c82847..be614f9 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -42,6 +42,14 @@
   Transaction();
   ~Transaction();
 
+  void Abort(const std::string& abort_message)
+      LOCKS_EXCLUDED(log_lock_)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void ThrowInternalError(Thread* self)
+      LOCKS_EXCLUDED(log_lock_)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  bool IsAborted() LOCKS_EXCLUDED(log_lock_);
+
   // Record object field changes.
   void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value,
                                bool is_volatile)
@@ -85,7 +93,7 @@
       LOCKS_EXCLUDED(log_lock_);
 
   // Abort transaction by undoing all recorded changes.
-  void Abort()
+  void Rollback()
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
       LOCKS_EXCLUDED(log_lock_);
 
@@ -206,10 +214,14 @@
       EXCLUSIVE_LOCKS_REQUIRED(log_lock_)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
+  const std::string& GetAbortMessage() LOCKS_EXCLUDED(log_lock_);
+
   Mutex log_lock_ ACQUIRED_AFTER(Locks::intern_table_lock_);
   std::map<mirror::Object*, ObjectLog> object_logs_ GUARDED_BY(log_lock_);
   std::map<mirror::Array*, ArrayLog> array_logs_  GUARDED_BY(log_lock_);
   std::list<InternStringLog> intern_string_logs_ GUARDED_BY(log_lock_);
+  bool aborted_ GUARDED_BY(log_lock_);
+  std::string abort_message_ GUARDED_BY(log_lock_);
 
   DISALLOW_COPY_AND_ASSIGN(Transaction);
 };
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index 8c4b90d..b80fe22 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -24,8 +24,68 @@
 
 namespace art {
 
-class TransactionTest : public CommonRuntimeTest {};
+class TransactionTest : public CommonRuntimeTest {
+ public:
+  // Tests failing class initialization due to native call with transaction rollback.
+  void testTransactionAbort(const char* tested_class_signature) {
+    ScopedObjectAccess soa(Thread::Current());
+    jobject jclass_loader = LoadDex("Transaction");
+    StackHandleScope<2> hs(soa.Self());
+    Handle<mirror::ClassLoader> class_loader(
+        hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
+    ASSERT_TRUE(class_loader.Get() != nullptr);
 
+    // Load and initialize java.lang.ExceptionInInitializerError and java.lang.InternalError
+    // classes so they can be thrown during class initialization if the transaction aborts.
+    MutableHandle<mirror::Class> h_klass(
+        hs.NewHandle(class_linker_->FindSystemClass(soa.Self(),
+                                                    "Ljava/lang/ExceptionInInitializerError;")));
+    ASSERT_TRUE(h_klass.Get() != nullptr);
+    class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+    ASSERT_TRUE(h_klass->IsInitialized());
+
+    h_klass.Assign(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/InternalError;"));
+    ASSERT_TRUE(h_klass.Get() != nullptr);
+    class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+    ASSERT_TRUE(h_klass->IsInitialized());
+
+    // Load and verify utility class.
+    h_klass.Assign(class_linker_->FindClass(soa.Self(), "LTransaction$AbortHelperClass;",
+                                            class_loader));
+    ASSERT_TRUE(h_klass.Get() != nullptr);
+    class_linker_->VerifyClass(soa.Self(), h_klass);
+    ASSERT_TRUE(h_klass->IsVerified());
+
+    // Load and verify tested class.
+    h_klass.Assign(class_linker_->FindClass(soa.Self(), tested_class_signature, class_loader));
+    ASSERT_TRUE(h_klass.Get() != nullptr);
+    class_linker_->VerifyClass(soa.Self(), h_klass);
+    ASSERT_TRUE(h_klass->IsVerified());
+
+    mirror::Class::Status old_status = h_klass->GetStatus();
+    uint32_t old_lock_word = h_klass->GetLockWord(false).GetValue();
+
+    Transaction transaction;
+    Runtime::Current()->EnterTransactionMode(&transaction);
+    bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+    Runtime::Current()->ExitTransactionMode();
+    ASSERT_FALSE(success);
+    ASSERT_TRUE(h_klass->IsErroneous());
+    ASSERT_TRUE(soa.Self()->IsExceptionPending());
+    ASSERT_TRUE(transaction.IsAborted());
+
+    // Check class's monitor get back to its original state without rolling back changes.
+    uint32_t new_lock_word = h_klass->GetLockWord(false).GetValue();
+    EXPECT_EQ(old_lock_word, new_lock_word);
+
+    // Check class status is rolled back properly.
+    soa.Self()->ClearException();
+    transaction.Rollback();
+    ASSERT_EQ(old_status, h_klass->GetStatus());
+  }
+};
+
+// Tests object's class is preserved after transaction rollback.
 TEST_F(TransactionTest, Object_class) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
@@ -40,11 +100,12 @@
   ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
   Runtime::Current()->ExitTransactionMode();
 
-  // Aborting transaction must not clear the Object::class field.
-  transaction.Abort();
+  // Rolling back transaction's changes must not clear the Object::class field.
+  transaction.Rollback();
   EXPECT_EQ(h_obj->GetClass(), h_klass.Get());
 }
 
+// Tests object's monitor state is preserved after transaction rollback.
 TEST_F(TransactionTest, Object_monitor) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
@@ -66,13 +127,14 @@
   uint32_t new_lock_word = h_obj->GetLockWord(false).GetValue();
   Runtime::Current()->ExitTransactionMode();
 
-  // Aborting transaction must not clear the Object::class field.
-  transaction.Abort();
+  // Rolling back transaction's changes must not change monitor's state.
+  transaction.Rollback();
   uint32_t aborted_lock_word = h_obj->GetLockWord(false).GetValue();
   EXPECT_NE(old_lock_word, new_lock_word);
   EXPECT_EQ(aborted_lock_word, new_lock_word);
 }
 
+// Tests array's length is preserved after transaction rollback.
 TEST_F(TransactionTest, Array_length) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
@@ -95,11 +157,12 @@
   ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
   Runtime::Current()->ExitTransactionMode();
 
-  // Aborting transaction must not clear the Object::class field.
-  transaction.Abort();
+  // Rolling back transaction's changes must not reset array's length.
+  transaction.Rollback();
   EXPECT_EQ(h_obj->GetLength(), kArraySize);
 }
 
+// Tests static fields are reset to their default value after transaction rollback.
 TEST_F(TransactionTest, StaticFieldsTest) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<4> hs(soa.Self());
@@ -110,8 +173,10 @@
   Handle<mirror::Class> h_klass(
       hs.NewHandle(class_linker_->FindClass(soa.Self(), "LStaticFieldsTest;", class_loader)));
   ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  ASSERT_TRUE(success);
   ASSERT_TRUE(h_klass->IsInitialized());
+  ASSERT_FALSE(soa.Self()->IsExceptionPending());
 
   // Lookup fields.
   mirror::ArtField* booleanField = h_klass->FindDeclaredStaticField("booleanField", "Z");
@@ -155,7 +220,7 @@
   ASSERT_DOUBLE_EQ(doubleField->GetDouble(h_klass.Get()), static_cast<double>(0.0));
 
   mirror::ArtField* objectField = h_klass->FindDeclaredStaticField("objectField",
-                                                                      "Ljava/lang/Object;");
+                                                                   "Ljava/lang/Object;");
   ASSERT_TRUE(objectField != nullptr);
   ASSERT_EQ(objectField->GetTypeAsPrimitiveType(), Primitive::kPrimNot);
   ASSERT_EQ(objectField->GetObject(h_klass.Get()), nullptr);
@@ -168,7 +233,7 @@
   ASSERT_TRUE(h_obj.Get() != nullptr);
   ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
 
-  // Modify fields inside transaction and abort it.
+  // Modify fields inside transaction then rollback changes.
   Transaction transaction;
   Runtime::Current()->EnterTransactionMode(&transaction);
   booleanField->SetBoolean<true>(h_klass.Get(), true);
@@ -181,7 +246,7 @@
   doubleField->SetDouble<true>(h_klass.Get(), 1.0);
   objectField->SetObject<true>(h_klass.Get(), h_obj.Get());
   Runtime::Current()->ExitTransactionMode();
-  transaction.Abort();
+  transaction.Rollback();
 
   // Check values have properly been restored to their original (default) value.
   EXPECT_EQ(booleanField->GetBoolean(h_klass.Get()), false);
@@ -195,6 +260,7 @@
   EXPECT_EQ(objectField->GetObject(h_klass.Get()), nullptr);
 }
 
+// Tests instance fields are reset to their default value after transaction rollback.
 TEST_F(TransactionTest, InstanceFieldsTest) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<5> hs(soa.Self());
@@ -205,8 +271,10 @@
   Handle<mirror::Class> h_klass(
       hs.NewHandle(class_linker_->FindClass(soa.Self(), "LInstanceFieldsTest;", class_loader)));
   ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  ASSERT_TRUE(success);
   ASSERT_TRUE(h_klass->IsInitialized());
+  ASSERT_FALSE(soa.Self()->IsExceptionPending());
 
   // Allocate an InstanceFieldTest object.
   Handle<mirror::Object> h_instance(hs.NewHandle(h_klass->AllocObject(soa.Self())));
@@ -267,7 +335,7 @@
   ASSERT_TRUE(h_obj.Get() != nullptr);
   ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
 
-  // Modify fields inside transaction and abort it.
+  // Modify fields inside transaction then rollback changes.
   Transaction transaction;
   Runtime::Current()->EnterTransactionMode(&transaction);
   booleanField->SetBoolean<true>(h_instance.Get(), true);
@@ -280,7 +348,7 @@
   doubleField->SetDouble<true>(h_instance.Get(), 1.0);
   objectField->SetObject<true>(h_instance.Get(), h_obj.Get());
   Runtime::Current()->ExitTransactionMode();
-  transaction.Abort();
+  transaction.Rollback();
 
   // Check values have properly been restored to their original (default) value.
   EXPECT_EQ(booleanField->GetBoolean(h_instance.Get()), false);
@@ -294,7 +362,7 @@
   EXPECT_EQ(objectField->GetObject(h_instance.Get()), nullptr);
 }
 
-
+// Tests static array fields are reset to their default value after transaction rollback.
 TEST_F(TransactionTest, StaticArrayFieldsTest) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<4> hs(soa.Self());
@@ -305,8 +373,10 @@
   Handle<mirror::Class> h_klass(
       hs.NewHandle(class_linker_->FindClass(soa.Self(), "LStaticArrayFieldsTest;", class_loader)));
   ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  ASSERT_TRUE(success);
   ASSERT_TRUE(h_klass->IsInitialized());
+  ASSERT_FALSE(soa.Self()->IsExceptionPending());
 
   // Lookup fields.
   mirror::ArtField* booleanArrayField = h_klass->FindDeclaredStaticField("booleanArrayField", "[Z");
@@ -382,7 +452,7 @@
   ASSERT_TRUE(h_obj.Get() != nullptr);
   ASSERT_EQ(h_obj->GetClass(), h_klass.Get());
 
-  // Modify fields inside transaction and abort it.
+  // Modify fields inside transaction then rollback changes.
   Transaction transaction;
   Runtime::Current()->EnterTransactionMode(&transaction);
   booleanArray->SetWithoutChecks<true>(0, true);
@@ -395,7 +465,7 @@
   doubleArray->SetWithoutChecks<true>(0, 1.0);
   objectArray->SetWithoutChecks<true>(0, h_obj.Get());
   Runtime::Current()->ExitTransactionMode();
-  transaction.Abort();
+  transaction.Rollback();
 
   // Check values have properly been restored to their original (default) value.
   EXPECT_EQ(booleanArray->GetWithoutChecks(0), false);
@@ -409,6 +479,7 @@
   EXPECT_EQ(objectArray->GetWithoutChecks(0), nullptr);
 }
 
+// Tests successful class initialization without class initializer.
 TEST_F(TransactionTest, EmptyClass) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
@@ -417,18 +488,22 @@
   ASSERT_TRUE(class_loader.Get() != nullptr);
 
   Handle<mirror::Class> h_klass(
-      hs.NewHandle(class_linker_->FindClass(soa.Self(), "LTransaction$EmptyStatic;", class_loader)));
+      hs.NewHandle(class_linker_->FindClass(soa.Self(), "LTransaction$EmptyStatic;",
+                                            class_loader)));
   ASSERT_TRUE(h_klass.Get() != nullptr);
   class_linker_->VerifyClass(soa.Self(), h_klass);
   ASSERT_TRUE(h_klass->IsVerified());
 
   Transaction transaction;
   Runtime::Current()->EnterTransactionMode(&transaction);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
   Runtime::Current()->ExitTransactionMode();
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(h_klass->IsInitialized());
   ASSERT_FALSE(soa.Self()->IsExceptionPending());
 }
 
+// Tests successful class initialization with class initializer.
 TEST_F(TransactionTest, StaticFieldClass) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
@@ -445,51 +520,37 @@
 
   Transaction transaction;
   Runtime::Current()->EnterTransactionMode(&transaction);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
+  bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
   Runtime::Current()->ExitTransactionMode();
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(h_klass->IsInitialized());
   ASSERT_FALSE(soa.Self()->IsExceptionPending());
 }
 
-TEST_F(TransactionTest, BlacklistedClass) {
-  ScopedObjectAccess soa(Thread::Current());
-  jobject jclass_loader = LoadDex("Transaction");
-  StackHandleScope<2> hs(soa.Self());
-  Handle<mirror::ClassLoader> class_loader(
-      hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
-  ASSERT_TRUE(class_loader.Get() != nullptr);
-
-  // Load and verify java.lang.ExceptionInInitializerError and java.lang.InternalError which will
-  // be thrown by class initialization due to native call.
-  MutableHandle<mirror::Class> h_klass(
-      hs.NewHandle(class_linker_->FindSystemClass(soa.Self(),
-                                                  "Ljava/lang/ExceptionInInitializerError;")));
-  ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->VerifyClass(soa.Self(), h_klass);
-  ASSERT_TRUE(h_klass->IsVerified());
-  h_klass.Assign(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/InternalError;"));
-  ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->VerifyClass(soa.Self(), h_klass);
-  ASSERT_TRUE(h_klass->IsVerified());
-
-  // Load and verify Transaction$NativeSupport used in class initialization.
-  h_klass.Assign(class_linker_->FindClass(soa.Self(), "LTransaction$NativeSupport;",
-                                             class_loader));
-  ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->VerifyClass(soa.Self(), h_klass);
-  ASSERT_TRUE(h_klass->IsVerified());
-
-  h_klass.Assign(class_linker_->FindClass(soa.Self(), "LTransaction$BlacklistedClass;",
-                                             class_loader));
-  ASSERT_TRUE(h_klass.Get() != nullptr);
-  class_linker_->VerifyClass(soa.Self(), h_klass);
-  ASSERT_TRUE(h_klass->IsVerified());
-
-  Transaction transaction;
-  Runtime::Current()->EnterTransactionMode(&transaction);
-  class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
-  Runtime::Current()->ExitTransactionMode();
-  ASSERT_TRUE(soa.Self()->IsExceptionPending());
+// Tests failing class initialization due to native call.
+TEST_F(TransactionTest, NativeCallAbortClass) {
+  testTransactionAbort("LTransaction$NativeCallAbortClass;");
 }
 
+// Tests failing class initialization due to native call in a "synchronized" statement
+// (which must catch any exception, do the monitor-exit then re-throw the caught exception).
+TEST_F(TransactionTest, SynchronizedNativeCallAbortClass) {
+  testTransactionAbort("LTransaction$SynchronizedNativeCallAbortClass;");
+}
 
+// Tests failing class initialization due to native call, even if an "all" catch handler
+// catches the exception thrown when aborting the transaction.
+TEST_F(TransactionTest, CatchNativeCallAbortClass) {
+  testTransactionAbort("LTransaction$CatchNativeCallAbortClass;");
+}
+
+// Tests failing class initialization with multiple transaction aborts.
+TEST_F(TransactionTest, MultipleNativeCallAbortClass) {
+  testTransactionAbort("LTransaction$MultipleNativeCallAbortClass;");
+}
+
+// Tests failing class initialization due to allocating instance of finalizable class.
+TEST_F(TransactionTest, FinalizableAbortClass) {
+  testTransactionAbort("LTransaction$FinalizableAbortClass;");
+}
 }  // namespace art
diff --git a/test/Transaction/Transaction.java b/test/Transaction/Transaction.java
index 9ca7fbf..00e1fbb 100644
--- a/test/Transaction/Transaction.java
+++ b/test/Transaction/Transaction.java
@@ -25,13 +25,57 @@
       }
     }
 
-    static class BlacklistedClass {
+    static class FinalizableAbortClass {
+      public static AbortHelperClass finalizableObject;
       static {
-        NativeSupport.native_call();
+        finalizableObject = new AbortHelperClass();
       }
     }
 
-    static class NativeSupport {
-      public static native void native_call();
+    static class NativeCallAbortClass {
+      static {
+        AbortHelperClass.nativeMethod();
+      }
+    }
+
+    static class SynchronizedNativeCallAbortClass {
+      static {
+        synchronized (SynchronizedNativeCallAbortClass.class) {
+          AbortHelperClass.nativeMethod();
+        }
+      }
+    }
+
+    static class CatchNativeCallAbortClass {
+      static {
+        try {
+          AbortHelperClass.nativeMethod();
+        } catch (Throwable e) {
+          // ignore exception.
+        }
+      }
+    }
+
+    static class MultipleNativeCallAbortClass {
+      static {
+        // Call native method but catch the transaction exception.
+        try {
+          AbortHelperClass.nativeMethod();
+        } catch (Throwable e) {
+          // ignore exception.
+        }
+
+        // Call another native method.
+        AbortHelperClass.nativeMethod2();
+      }
+    }
+
+    // Helper class to abort transaction: finalizable class with natve methods.
+    static class AbortHelperClass {
+      public void finalize() throws Throwable {
+        super.finalize();
+      }
+      public static native void nativeMethod();
+      public static native void nativeMethod2();
     }
 }