summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Hertz <shertz@google.com>2015-02-05 08:19:21 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2015-02-05 08:19:22 +0000
commit911b4be50c379d052dd9e5f9b59ce91df1340453 (patch)
tree790b7f3a25b0a31ddddc606b1ba2a24fc8146476
parent7e6a918ecfbe786c060bce0eeddd55c4c70f819d (diff)
parent1c80becf5406cd6d95dc24bf47a0c5a3809ea281 (diff)
downloadart-911b4be50c379d052dd9e5f9b59ce91df1340453.zip
art-911b4be50c379d052dd9e5f9b59ce91df1340453.tar.gz
art-911b4be50c379d052dd9e5f9b59ce91df1340453.tar.bz2
Merge "Fix transaction aborting"
-rw-r--r--compiler/driver/compiler_driver.cc2
-rw-r--r--runtime/class_linker.cc8
-rw-r--r--runtime/interpreter/interpreter_common.cc8
-rw-r--r--runtime/runtime.cc25
-rw-r--r--runtime/runtime.h7
-rw-r--r--runtime/transaction.cc34
-rw-r--r--runtime/transaction.h14
-rw-r--r--runtime/transaction_test.cc179
-rw-r--r--test/Transaction/Transaction.java52
9 files changed, 259 insertions, 70 deletions
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 @@ static void InitializeClass(const ParallelCompilationManager* manager, size_t cl
*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 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
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 @@ static inline void AssignRegister(ShadowFrame* new_shadow_frame, const ShadowFra
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 @@ void Runtime::ExitTransactionMode() {
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 @@ class Runtime {
}
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 @@ namespace art {
// 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 @@ Transaction::~Transaction() {
}
}
+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 @@ void Transaction::LogInternedString(const InternStringLog& log) {
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 @@ class Transaction FINAL {
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 @@ class Transaction FINAL {
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 @@ class Transaction FINAL {
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 @@ TEST_F(TransactionTest, Object_class) {
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 @@ TEST_F(TransactionTest, Object_monitor) {
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 @@ TEST_F(TransactionTest, Array_length) {
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 @@ TEST_F(TransactionTest, StaticFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticFieldsTest) {
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 @@ TEST_F(TransactionTest, InstanceFieldsTest) {
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 @@ TEST_F(TransactionTest, InstanceFieldsTest) {
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 @@ TEST_F(TransactionTest, InstanceFieldsTest) {
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 @@ TEST_F(TransactionTest, InstanceFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) {
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 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) {
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 @@ TEST_F(TransactionTest, EmptyClass) {
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 @@ TEST_F(TransactionTest, StaticFieldClass) {
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());
+// Tests failing class initialization due to native call.
+TEST_F(TransactionTest, NativeCallAbortClass) {
+ testTransactionAbort("LTransaction$NativeCallAbortClass;");
+}
- 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());
+// 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;");
+}
- 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, 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 @@ public class Transaction {
}
}
- 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();
}
}