Use specific exception class to abort transaction

We used to throw a java.lang.InternalError when aborting a
transaction (when preinitializing image classes at compilation time).

We now use dedicated class dalvik.system.TransactionAbortError that
is only thrown by the compiler to abort a transaction. This class has
constructors taking a java.lang.Throwable "cause" so we can wrap
exceptions causing the transaction to abort (for instance class
java.lang.ClassNotFoundException) and give more information about the
cause of the transaction abort.

Bug: 20019689
Change-Id: I019a72a1c754d8bba6a7ad6bb0f02e4fd6668622
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index a89196d..be4b9e9 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3411,7 +3411,7 @@
       // so we need to throw it again now.
       VLOG(compiler) << "Return from class initializer of " << PrettyDescriptor(klass.Get())
                      << " without exception while transaction was aborted: re-throw it now.";
-      Runtime::Current()->ThrowInternalErrorForAbortedTransaction(self);
+      Runtime::Current()->ThrowTransactionAbortError(self);
       mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
       success = false;
     } else {
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index a310452..582843c 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -475,7 +475,7 @@
   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);
+  Runtime::Current()->AbortTransactionAndThrowAbortError(self, abort_msg);
   va_end(args);
 }
 
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 281f332..2aa4af4 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -37,6 +37,7 @@
 #include "mirror/string-inl.h"
 #include "nth_caller_visitor.h"
 #include "thread.h"
+#include "transaction.h"
 #include "well_known_classes.h"
 
 namespace art {
@@ -87,13 +88,14 @@
 // Common helper for class-loading cutouts in an unstarted runtime. We call Runtime methods that
 // rely on Java code to wrap errors in the correct exception class (i.e., NoClassDefFoundError into
 // ClassNotFoundException), so need to do the same. The only exception is if the exception is
-// actually InternalError. This must not be wrapped, as it signals an initialization abort.
+// actually the transaction abort exception. This must not be wrapped, as it signals an
+// initialization abort.
 static void CheckExceptionGenerateClassNotFound(Thread* self)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   if (self->IsExceptionPending()) {
-    // If it is not an InternalError, wrap it.
+    // If it is not the transaction abort exception, wrap it.
     std::string type(PrettyTypeOf(self->GetException()));
-    if (type != "java.lang.InternalError") {
+    if (type != Transaction::kAbortExceptionDescriptor) {
       self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;",
                                      "ClassNotFoundException");
     }
@@ -502,7 +504,7 @@
   }
   if (!have_dex) {
     self->ClearException();
-    Runtime::Current()->AbortTransactionAndThrowInternalError(self, "Could not create Dex object");
+    Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Could not create Dex object");
   }
 }
 
@@ -570,7 +572,7 @@
   int64_t address_long = shadow_frame->GetVRegLong(arg_offset);
   mirror::Object* obj = shadow_frame->GetVRegReference(arg_offset + 2);
   if (obj == nullptr) {
-    Runtime::Current()->AbortTransactionAndThrowInternalError(self, "Null pointer in peekArray");
+    Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Null pointer in peekArray");
     return;
   }
   mirror::Array* array = obj->AsArray();
@@ -580,7 +582,7 @@
   if (offset < 0 || offset + count > array->GetLength()) {
     std::string error_msg(StringPrintf("Array out of bounds in peekArray: %d/%d vs %d",
                                        offset, count, array->GetLength()));
-    Runtime::Current()->AbortTransactionAndThrowInternalError(self, error_msg.c_str());
+    Runtime::Current()->AbortTransactionAndThrowAbortError(self, error_msg.c_str());
     return;
   }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index b5d2e15..b82e897 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1533,21 +1533,20 @@
   }
 }
 
-void Runtime::AbortTransactionAndThrowInternalError(Thread* self,
-                                                    const std::string& abort_message) {
+void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   // Throwing an exception may cause its class initialization. If we mark the transaction
   // aborted before that, we may warn with a false alarm. Throwing the exception before
   // marking the transaction aborted avoids that.
-  preinitialization_transaction_->ThrowInternalError(self, false);
+  preinitialization_transaction_->ThrowAbortError(self, false);
   preinitialization_transaction_->Abort(abort_message);
 }
 
-void Runtime::ThrowInternalErrorForAbortedTransaction(Thread* self) {
+void Runtime::ThrowTransactionAbortError(Thread* self) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->ThrowInternalError(self, true);
+  preinitialization_transaction_->ThrowAbortError(self, true);
 }
 
 void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 64b7183..af3d899 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -468,9 +468,9 @@
   void ExitTransactionMode();
   bool IsTransactionAborted() const;
 
-  void AbortTransactionAndThrowInternalError(Thread* self, const std::string& abort_message)
+  void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void ThrowInternalErrorForAbortedTransaction(Thread* self)
+  void ThrowTransactionAbortError(Thread* self)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   void RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset, uint8_t value,
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 186cfea..5b8d23b 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -60,8 +60,8 @@
 
 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 may abort more than once if the exception thrown at the time of the
+  // previous 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_) {
@@ -70,16 +70,17 @@
   }
 }
 
-void Transaction::ThrowInternalError(Thread* self, bool rethrow) {
+void Transaction::ThrowAbortError(Thread* self, bool rethrow) {
   if (kIsDebugBuild && rethrow) {
-    CHECK(IsAborted()) << "Rethrow InternalError while transaction is not aborted";
+    CHECK(IsAborted()) << "Rethrow " << Transaction::kAbortExceptionDescriptor
+                       << " while transaction is not aborted";
   }
   std::string abort_msg(GetAbortMessage());
   // Temporary workaround for b/20019689.
   if (self->IsExceptionPending()) {
     self->ClearException();
   }
-  self->ThrowNewException("Ljava/lang/InternalError;", abort_msg.c_str());
+  self->ThrowNewException(Transaction::kAbortExceptionSignature, abort_msg.c_str());
 }
 
 bool Transaction::IsAborted() {
diff --git a/runtime/transaction.h b/runtime/transaction.h
index e1b93c9..1419a38 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -39,13 +39,16 @@
 
 class Transaction FINAL {
  public:
+  static constexpr const char* kAbortExceptionDescriptor = "dalvik.system.TransactionAbortError";
+  static constexpr const char* kAbortExceptionSignature = "Ldalvik/system/TransactionAbortError;";
+
   Transaction();
   ~Transaction();
 
   void Abort(const std::string& abort_message)
       LOCKS_EXCLUDED(log_lock_)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void ThrowInternalError(Thread* self, bool rethrow)
+  void ThrowAbortError(Thread* self, bool rethrow)
       LOCKS_EXCLUDED(log_lock_)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   bool IsAborted() LOCKS_EXCLUDED(log_lock_);
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index 5db51c8..24ecf6b 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -35,8 +35,9 @@
         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.
+    // Load and initialize java.lang.ExceptionInInitializerError and the exception class used
+    // to abort transaction 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;")));
@@ -44,7 +45,8 @@
     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;"));
+    h_klass.Assign(class_linker_->FindSystemClass(soa.Self(),
+                                                  Transaction::kAbortExceptionSignature));
     ASSERT_TRUE(h_klass.Get() != nullptr);
     class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true);
     ASSERT_TRUE(h_klass->IsInitialized());