diff options
-rw-r--r-- | build/Android.gtest.mk | 4 | ||||
-rw-r--r-- | runtime/aot_class_linker.cc | 2 | ||||
-rw-r--r-- | runtime/common_runtime_test.cc | 37 | ||||
-rw-r--r-- | runtime/common_runtime_test.h | 10 | ||||
-rw-r--r-- | runtime/dex2oat_environment_test.h | 23 | ||||
-rw-r--r-- | runtime/interpreter/interpreter_common.cc | 26 | ||||
-rw-r--r-- | runtime/interpreter/unstarted_runtime_test.cc | 42 | ||||
-rw-r--r-- | runtime/runtime.cc | 16 | ||||
-rw-r--r-- | runtime/runtime.h | 1 | ||||
-rw-r--r-- | runtime/transaction.cc | 54 | ||||
-rw-r--r-- | runtime/transaction.h | 17 | ||||
-rw-r--r-- | runtime/transaction_test.cc | 101 |
12 files changed, 212 insertions, 121 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index c548fdd6ef..ff0f891836 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -254,6 +254,10 @@ ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_DEFAULT_64) $(TARGE ART_GTEST_two_runtimes_test_HOST_DEPS := $(HOST_CORE_IMAGE_DEFAULT_64) $(HOST_CORE_IMAGE_DEFAULT_32) ART_GTEST_two_runtimes_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_DEFAULT_64) $(TARGET_CORE_IMAGE_DEFAULT_32) +# The transaction test has dependencies on core.oat. +ART_GTEST_transaction_test_HOST_DEPS := $(HOST_CORE_IMAGE_DEFAULT_64) $(HOST_CORE_IMAGE_DEFAULT_32) +ART_GTEST_transaction_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_DEFAULT_64) $(TARGET_CORE_IMAGE_DEFAULT_32) + ART_GTEST_dex2oat_environment_tests_HOST_DEPS := \ $(HOST_CORE_IMAGE_optimizing_64) \ $(HOST_CORE_IMAGE_optimizing_32) \ diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc index c151306c01..79b6c26449 100644 --- a/runtime/aot_class_linker.cc +++ b/runtime/aot_class_linker.cc @@ -72,7 +72,7 @@ bool AotClassLinker::InitializeClass(Thread* self, } if (strict_mode_) { - runtime->EnterTransactionMode(true, klass.Get()->AsClass().Ptr()); + runtime->EnterTransactionMode(/*strict=*/ true, klass.Get()); } bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents); diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 05ec9e6d54..135dc7be73 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -433,6 +433,43 @@ bool CommonRuntimeTestImpl::StartDex2OatCommandLine(/*out*/std::vector<std::stri return true; } +std::string CommonRuntimeTestImpl::GetImageDirectory() { + if (IsHost()) { + const char* host_dir = getenv("ANDROID_HOST_OUT"); + CHECK(host_dir != nullptr); + return std::string(host_dir) + "/framework"; + } else { + return std::string("/data/art-test"); + } +} + +std::string CommonRuntimeTestImpl::GetImageLocation() { + return GetImageDirectory() + "/core.art"; +} + +std::string CommonRuntimeTestImpl::GetSystemImageFile() { + return GetImageDirectory() + "/" + GetInstructionSetString(kRuntimeISA) + "/core.art"; +} + +void CommonRuntimeTestImpl::EnterTransactionMode() { + CHECK(!Runtime::Current()->IsActiveTransaction()); + Runtime::Current()->EnterTransactionMode(/*strict=*/ false, /*root=*/ nullptr); +} + +void CommonRuntimeTestImpl::ExitTransactionMode() { + Runtime::Current()->ExitTransactionMode(); + CHECK(!Runtime::Current()->IsActiveTransaction()); +} + +void CommonRuntimeTestImpl::RollbackAndExitTransactionMode() { + Runtime::Current()->RollbackAndExitTransactionMode(); + CHECK(!Runtime::Current()->IsActiveTransaction()); +} + +bool CommonRuntimeTestImpl::IsTransactionAborted() { + return Runtime::Current()->IsTransactionAborted(); +} + CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) { vm_->SetCheckJniAbortHook(Hook, &actual_); } diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 3595c7309d..e1ab3dfd7f 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -168,6 +168,16 @@ class CommonRuntimeTestImpl : public CommonArtTestImpl { // Called to finish up runtime creation and filling test fields. By default runs root // initializers, initialize well-known classes, and creates the heap thread pool. virtual void FinalizeSetup(); + + // Returns the directory where the pre-compiled core.art can be found. + static std::string GetImageDirectory(); + static std::string GetImageLocation(); + static std::string GetSystemImageFile(); + + static void EnterTransactionMode(); + static void ExitTransactionMode(); + static void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_); + static bool IsTransactionAborted(); }; template <typename TestType> diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index fbcee3901f..0d74dbb936 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -137,29 +137,6 @@ class Dex2oatEnvironmentTest : public CommonRuntimeTest { dst_stream << src_stream.rdbuf(); } - // Returns the directory where the pre-compiled core.art can be found. - // TODO: We should factor out this into common tests somewhere rather than - // re-hardcoding it here (This was copied originally from the elf writer - // test). - std::string GetImageDirectory() const { - if (IsHost()) { - const char* host_dir = getenv("ANDROID_HOST_OUT"); - CHECK(host_dir != nullptr); - return std::string(host_dir) + "/framework"; - } else { - return std::string("/data/art-test"); - } - } - - std::string GetImageLocation() const { - return GetImageDirectory() + "/core.art"; - } - - std::string GetSystemImageFile() const { - return GetImageDirectory() + "/" + GetInstructionSetString(kRuntimeISA) - + "/core.art"; - } - // Returns the path to an image location whose contents differ from the // image at GetImageLocation(). This is used for testing mismatched // image checksums in the oat_file_assistant_tests. diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 9953743f04..e017b17c0a 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -109,7 +109,7 @@ bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst if (is_static) { obj = f->GetDeclaringClass(); if (transaction_active) { - if (Runtime::Current()->GetTransaction()->ReadConstraint(obj.Ptr(), f)) { + if (Runtime::Current()->GetTransaction()->ReadConstraint(self, obj, f)) { Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Can't read static fields of " + obj->PrettyTypeOf() + " since it does not belong to clinit's class."); return false; @@ -321,14 +321,6 @@ bool DoFieldPut(Thread* self, const ShadowFrame& shadow_frame, const Instruction ObjPtr<mirror::Object> obj; if (is_static) { obj = f->GetDeclaringClass(); - if (transaction_active) { - if (Runtime::Current()->GetTransaction()->WriteConstraint(obj.Ptr(), f)) { - Runtime::Current()->AbortTransactionAndThrowAbortError( - self, "Can't set fields of " + obj->PrettyTypeOf()); - return false; - } - } - } else { obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data)); if (UNLIKELY(obj == nullptr)) { @@ -336,6 +328,22 @@ bool DoFieldPut(Thread* self, const ShadowFrame& shadow_frame, const Instruction return false; } } + if (transaction_active) { + Runtime* runtime = Runtime::Current(); + if (runtime->GetTransaction()->WriteConstraint(self, obj, f)) { + if (is_static) { + runtime->AbortTransactionAndThrowAbortError( + self, "Can't set fields of " + obj->PrettyTypeOf()); + } else { + // This can happen only when compiling a boot image extension. + DCHECK(!runtime->GetTransaction()->IsStrict()); + DCHECK(runtime->GetHeap()->ObjectIsInBootImageSpace(obj)); + runtime->AbortTransactionAndThrowAbortError( + self, "Can't set fields of boot image objects"); + } + return false; + } + } uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data); JValue value = GetFieldValue<field_type>(shadow_frame, vregA); diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index 495039c7fd..4429f6371d 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -785,19 +785,19 @@ TEST_F(UnstartedRuntimeTest, ToLowerUpper) { { JValue result; tmp->SetVReg(0, static_cast<int32_t>(i)); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } { JValue result; tmp->SetVReg(0, static_cast<int32_t>(i)); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } } @@ -805,19 +805,19 @@ TEST_F(UnstartedRuntimeTest, ToLowerUpper) { { JValue result; tmp->SetVReg(0, static_cast<int32_t>(i)); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } { JValue result; tmp->SetVReg(0, static_cast<int32_t>(i)); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); } } @@ -980,10 +980,10 @@ TEST_F(UnstartedRuntimeTest, ThreadLocalGet) { UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, nullptr, caller_method, 0); shadow_frame->SetLink(caller_frame.get()); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); self->ClearException(); @@ -1050,10 +1050,10 @@ TEST_F(UnstartedRuntimeTest, ThreadCurrentThread) { PrepareForAborts(); { - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); UnstartedThreadCurrentThread(self, shadow_frame.get(), &result, 0); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); - Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(IsTransactionAborted()); + ExitTransactionMode(); ASSERT_TRUE(self->IsExceptionPending()); self->ClearException(); } @@ -1120,7 +1120,7 @@ class UnstartedClassForNameTest : public UnstartedRuntimeTest { CHECK(name_string != nullptr); if (in_transaction) { - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); } CHECK(!self->IsExceptionPending()); @@ -1132,13 +1132,13 @@ class UnstartedClassForNameTest : public UnstartedRuntimeTest { } else { CHECK(self->IsExceptionPending()) << name; if (in_transaction) { - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); + ASSERT_TRUE(IsTransactionAborted()); } self->ClearException(); } if (in_transaction) { - Runtime::Current()->ExitTransactionMode(); + ExitTransactionMode(); } } } diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 91a3c45006..0b4b43b5dc 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -2381,18 +2381,14 @@ bool Runtime::IsActiveTransaction() const { return !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack(); } -void Runtime::EnterTransactionMode() { - DCHECK(IsAotCompiler()); - DCHECK(!IsActiveTransaction()); - // Make initialized classes visibly initialized now. If that happened during the transaction - // and then the transaction was aborted, we would roll back the status update but not the - // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized. - GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true); - preinitialization_transactions_.push_back(std::make_unique<Transaction>()); -} - void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) { DCHECK(IsAotCompiler()); + if (preinitialization_transactions_.empty()) { // Top-level transaction? + // Make initialized classes visibly initialized now. If that happened during the transaction + // and then the transaction was aborted, we would roll back the status update but not the + // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized. + GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true); + } preinitialization_transactions_.push_back(std::make_unique<Transaction>(strict, root)); } diff --git a/runtime/runtime.h b/runtime/runtime.h index 9dd749349c..a276b8768b 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -500,7 +500,6 @@ class Runtime { // Transaction support. bool IsActiveTransaction() const; - void EnterTransactionMode(); void EnterTransactionMode(bool strict, mirror::Class* root); void ExitTransactionMode(); void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/transaction.cc b/runtime/transaction.cc index c534a423b8..9a51e0f00e 100644 --- a/runtime/transaction.cc +++ b/runtime/transaction.cc @@ -21,12 +21,15 @@ #include "base/mutex-inl.h" #include "base/stl_util.h" #include "gc/accounting/card_table-inl.h" +#include "gc/heap.h" #include "gc_root-inl.h" #include "intern_table.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" +#include "obj_ptr-inl.h" +#include "runtime.h" #include <list> @@ -35,17 +38,14 @@ namespace art { // TODO: remove (only used for debugging purpose). static constexpr bool kEnableTransactionStats = false; -Transaction::Transaction() - : log_lock_("transaction log lock", kTransactionLogLock), - aborted_(false), - rolling_back_(false), - strict_(false) { - CHECK(Runtime::Current()->IsAotCompiler()); -} - -Transaction::Transaction(bool strict, mirror::Class* root) : Transaction() { - strict_ = strict; - root_ = root; +Transaction::Transaction(bool strict, mirror::Class* root) + : log_lock_("transaction log lock", kTransactionLogLock), + aborted_(false), + rolling_back_(false), + heap_(strict ? nullptr : Runtime::Current()->GetHeap()), + root_(root) { + DCHECK_EQ(strict, IsStrict()); + DCHECK(Runtime::Current()->IsAotCompiler()); } Transaction::~Transaction() { @@ -111,35 +111,33 @@ bool Transaction::IsRollingBack() { return rolling_back_; } -bool Transaction::IsStrict() { - MutexLock mu(Thread::Current(), log_lock_); - return strict_; -} - const std::string& Transaction::GetAbortMessage() { MutexLock mu(Thread::Current(), log_lock_); return abort_message_; } -bool Transaction::WriteConstraint(mirror::Object* obj, ArtField* field) { - MutexLock mu(Thread::Current(), log_lock_); - if (strict_ // no constraint for boot image - && field->IsStatic() // no constraint instance updating - && obj != root_) { // modifying other classes' static field, fail - return true; +bool Transaction::WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) { + MutexLock mu(self, log_lock_); + if (IsStrict()) { + return field->IsStatic() && // no constraint instance updating + obj != root_; // modifying other classes' static field, fail + } else { + // For boot image extension, prevent changes in boot image. + // For boot image there are no boot image spaces and this returns false. + return heap_->ObjectIsInBootImageSpace(obj); } - return false; } -bool Transaction::ReadConstraint(mirror::Object* obj, ArtField* field) { +bool Transaction::ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) { DCHECK(field->IsStatic()); DCHECK(obj->IsClass()); - MutexLock mu(Thread::Current(), log_lock_); - if (!strict_ || // no constraint for boot image - obj == root_) { // self-updating, pass + MutexLock mu(self, log_lock_); + if (IsStrict()) { + return obj != root_; // fail if not self-updating + } else { + // For boot image and boot image extension, allow reading any field. return false; } - return true; } void Transaction::RecordWriteFieldBoolean(mirror::Object* obj, diff --git a/runtime/transaction.h b/runtime/transaction.h index de6edd2ff3..fa874b8e64 100644 --- a/runtime/transaction.h +++ b/runtime/transaction.h @@ -30,6 +30,9 @@ #include <map> namespace art { +namespace gc { +class Heap; +} // namespace gc namespace mirror { class Array; class Class; @@ -38,14 +41,14 @@ class Object; class String; } // namespace mirror class InternTable; +template<class MirrorType> class ObjPtr; class Transaction final { public: static constexpr const char* kAbortExceptionDescriptor = "dalvik.system.TransactionAbortError"; static constexpr const char* kAbortExceptionSignature = "Ldalvik/system/TransactionAbortError;"; - Transaction(); - explicit Transaction(bool strict, mirror::Class* root); + Transaction(bool strict, mirror::Class* root); ~Transaction(); void Abort(const std::string& abort_message) @@ -63,7 +66,9 @@ class Transaction final { // If the transaction is in strict mode, then all access of static fields will be constrained, // one class's clinit will not be allowed to read or modify another class's static fields, unless // the transaction is aborted. - bool IsStrict() REQUIRES(!log_lock_); + bool IsStrict() { + return heap_ == nullptr; + } // Record object field changes. void RecordWriteFieldBoolean(mirror::Object* obj, @@ -135,11 +140,11 @@ class Transaction final { REQUIRES(!log_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - bool ReadConstraint(mirror::Object* obj, ArtField* field) + bool ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) REQUIRES(!log_lock_) REQUIRES_SHARED(Locks::mutator_lock_); - bool WriteConstraint(mirror::Object* obj, ArtField* field) + bool WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) REQUIRES(!log_lock_) REQUIRES_SHARED(Locks::mutator_lock_); @@ -307,7 +312,7 @@ class Transaction final { std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_); bool aborted_ GUARDED_BY(log_lock_); bool rolling_back_; // Single thread, no race. - bool strict_ GUARDED_BY(log_lock_); + gc::Heap* const heap_; std::string abort_message_ GUARDED_BY(log_lock_); mirror::Class* root_ GUARDED_BY(log_lock_); diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc index 7ad741a7a0..ff93dc8ce5 100644 --- a/runtime/transaction_test.cc +++ b/runtime/transaction_test.cc @@ -28,7 +28,12 @@ namespace art { class TransactionTest : public CommonRuntimeTest { - public: + protected: + void SetUpRuntimeOptions(/*out*/RuntimeOptions* options) override { + // Set up the image location. + options->emplace_back("-Ximage:" + GetImageLocation(), nullptr); + } + // Tests failing class initialization due to native call with transaction rollback. void testTransactionAbort(const char* tested_class_signature) { ScopedObjectAccess soa(Thread::Current()); @@ -70,9 +75,9 @@ class TransactionTest : public CommonRuntimeTest { ClassStatus old_status = h_klass->GetStatus(); LockWord old_lock_word = h_klass->GetLockWord(false); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true); - ASSERT_TRUE(Runtime::Current()->IsTransactionAborted()); + ASSERT_TRUE(IsTransactionAborted()); ASSERT_FALSE(success); ASSERT_TRUE(h_klass->IsErroneous()); ASSERT_TRUE(soa.Self()->IsExceptionPending()); @@ -83,7 +88,7 @@ class TransactionTest : public CommonRuntimeTest { // Check class status is rolled back properly. soa.Self()->ClearException(); - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); ASSERT_EQ(old_status, h_klass->GetStatus()); } }; @@ -96,12 +101,12 @@ TEST_F(TransactionTest, Object_class) { hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;"))); ASSERT_TRUE(h_klass != nullptr); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); Handle<mirror::Object> h_obj(hs.NewHandle(h_klass->AllocObject(soa.Self()))); ASSERT_TRUE(h_obj != nullptr); ASSERT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); // Rolling back transaction's changes must not clear the Object::class field. - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); EXPECT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); } @@ -120,12 +125,12 @@ TEST_F(TransactionTest, Object_monitor) { h_obj->MonitorEnter(soa.Self()); LockWord old_lock_word = h_obj->GetLockWord(false); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); // Unlock object's monitor inside the transaction. h_obj->MonitorExit(soa.Self()); LockWord new_lock_word = h_obj->GetLockWord(false); // Rolling back transaction's changes must not change monitor's state. - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); LockWord aborted_lock_word = h_obj->GetLockWord(false); EXPECT_FALSE(LockWord::Equal<false>(old_lock_word, new_lock_word)); @@ -142,7 +147,7 @@ TEST_F(TransactionTest, Array_length) { constexpr int32_t kArraySize = 2; - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); // Allocate an array during transaction. Handle<mirror::Array> h_obj = hs.NewHandle( @@ -153,7 +158,7 @@ TEST_F(TransactionTest, Array_length) { Runtime::Current()->GetHeap()->GetCurrentAllocator())); ASSERT_TRUE(h_obj != nullptr); ASSERT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); // Rolling back transaction's changes must not reset array's length. EXPECT_EQ(h_obj->GetLength(), kArraySize); @@ -231,7 +236,7 @@ TEST_F(TransactionTest, StaticFieldsTest) { ASSERT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); // Modify fields inside transaction then rollback changes. - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); booleanField->SetBoolean<true>(h_klass.Get(), true); byteField->SetByte<true>(h_klass.Get(), 1); charField->SetChar<true>(h_klass.Get(), 1u); @@ -241,7 +246,7 @@ TEST_F(TransactionTest, StaticFieldsTest) { floatField->SetFloat<true>(h_klass.Get(), 1.0); doubleField->SetDouble<true>(h_klass.Get(), 1.0); objectField->SetObject<true>(h_klass.Get(), h_obj.Get()); - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); // Check values have properly been restored to their original (default) value. EXPECT_EQ(booleanField->GetBoolean(h_klass.Get()), false); @@ -331,7 +336,7 @@ TEST_F(TransactionTest, InstanceFieldsTest) { ASSERT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); // Modify fields inside transaction then rollback changes. - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); booleanField->SetBoolean<true>(h_instance.Get(), true); byteField->SetByte<true>(h_instance.Get(), 1); charField->SetChar<true>(h_instance.Get(), 1u); @@ -341,7 +346,7 @@ TEST_F(TransactionTest, InstanceFieldsTest) { floatField->SetFloat<true>(h_instance.Get(), 1.0); doubleField->SetDouble<true>(h_instance.Get(), 1.0); objectField->SetObject<true>(h_instance.Get(), h_obj.Get()); - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); // Check values have properly been restored to their original (default) value. EXPECT_EQ(booleanField->GetBoolean(h_instance.Get()), false); @@ -454,7 +459,7 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) { ASSERT_OBJ_PTR_EQ(h_obj->GetClass(), h_klass.Get()); // Modify fields inside transaction then rollback changes. - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); booleanArray->SetWithoutChecks<true>(0, true); byteArray->SetWithoutChecks<true>(0, 1); charArray->SetWithoutChecks<true>(0, 1u); @@ -464,7 +469,7 @@ TEST_F(TransactionTest, StaticArrayFieldsTest) { floatArray->SetWithoutChecks<true>(0, 1.0); doubleArray->SetWithoutChecks<true>(0, 1.0); objectArray->SetWithoutChecks<true>(0, h_obj.Get()); - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); // Check values have properly been restored to their original (default) value. EXPECT_EQ(booleanArray->GetWithoutChecks(0), false); @@ -506,7 +511,7 @@ TEST_F(TransactionTest, ResolveString) { EXPECT_TRUE(class_linker_->LookupString(string_idx, h_dex_cache.Get()) == nullptr); EXPECT_TRUE(h_dex_cache->GetResolvedString(string_idx) == nullptr); // Do the transaction, then roll back. - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true); ASSERT_TRUE(success); ASSERT_TRUE(h_klass->IsInitialized()); @@ -518,7 +523,7 @@ TEST_F(TransactionTest, ResolveString) { EXPECT_STREQ(s->ToModifiedUtf8().c_str(), kResolvedString); EXPECT_OBJ_PTR_EQ(s, h_dex_cache->GetResolvedString(string_idx)); } - Runtime::Current()->RollbackAndExitTransactionMode(); + RollbackAndExitTransactionMode(); // Check that the string did not stay resolved. EXPECT_TRUE(class_linker_->LookupString(string_idx, h_dex_cache.Get()) == nullptr); EXPECT_TRUE(h_dex_cache->GetResolvedString(string_idx) == nullptr); @@ -541,9 +546,9 @@ TEST_F(TransactionTest, EmptyClass) { class_linker_->VerifyClass(soa.Self(), h_klass); ASSERT_TRUE(h_klass->IsVerified()); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true); - Runtime::Current()->ExitTransactionMode(); + ExitTransactionMode(); ASSERT_TRUE(success); ASSERT_TRUE(h_klass->IsInitialized()); ASSERT_FALSE(soa.Self()->IsExceptionPending()); @@ -564,9 +569,9 @@ TEST_F(TransactionTest, StaticFieldClass) { class_linker_->VerifyClass(soa.Self(), h_klass); ASSERT_TRUE(h_klass->IsVerified()); - Runtime::Current()->EnterTransactionMode(); + EnterTransactionMode(); bool success = class_linker_->EnsureInitialized(soa.Self(), h_klass, true, true); - Runtime::Current()->ExitTransactionMode(); + ExitTransactionMode(); ASSERT_TRUE(success); ASSERT_TRUE(h_klass->IsInitialized()); ASSERT_FALSE(soa.Self()->IsExceptionPending()); @@ -598,4 +603,56 @@ TEST_F(TransactionTest, MultipleNativeCallAbortClass) { TEST_F(TransactionTest, FinalizableAbortClass) { testTransactionAbort("LTransaction$FinalizableAbortClass;"); } + +TEST_F(TransactionTest, Constraints) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<4> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("Transaction")))); + + Handle<mirror::Class> boolean_class = hs.NewHandle( + class_linker_->FindClass(soa.Self(), "Ljava/lang/Boolean;", class_loader)); + ArtField* true_field = + mirror::Class::FindField(soa.Self(), boolean_class.Get(), "TRUE", "Ljava/lang/Boolean;"); + ASSERT_TRUE(true_field != nullptr); + ASSERT_TRUE(true_field->IsStatic()); + Handle<mirror::Object> true_value = hs.NewHandle(true_field->GetObject(boolean_class.Get())); + ASSERT_TRUE(true_value != nullptr); + ArtField* value_field = + mirror::Class::FindField(soa.Self(), boolean_class.Get(), "value", "Z"); + ASSERT_TRUE(value_field != nullptr); + ASSERT_FALSE(value_field->IsStatic()); + + Handle<mirror::Class> static_field_class(hs.NewHandle( + class_linker_->FindClass(soa.Self(), "LTransaction$StaticFieldClass;", class_loader))); + ArtField* int_field = + mirror::Class::FindField(soa.Self(), static_field_class.Get(), "intField", "I"); + ASSERT_TRUE(int_field != nullptr); + + // Test non-strict transaction. + Transaction transaction(/*strict=*/ false, /*root=*/ nullptr); + // Static field in boot image. + ASSERT_TRUE(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(boolean_class.Get())); + EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), boolean_class.Get(), true_field)); + EXPECT_FALSE(transaction.ReadConstraint(soa.Self(), boolean_class.Get(), true_field)); + // Instance field in boot image. Do not check ReadConstraint(), it expects only static fields. + ASSERT_TRUE(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(true_value.Get())); + EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), true_value.Get(), value_field)); + // Static field not in boot image. + ASSERT_FALSE(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(static_field_class.Get())); + EXPECT_FALSE(transaction.WriteConstraint(soa.Self(), static_field_class.Get(), int_field)); + EXPECT_FALSE(transaction.ReadConstraint(soa.Self(), static_field_class.Get(), int_field)); + + // Test strict transaction. + Transaction strict_transaction(/*strict=*/ true, /*root=*/ static_field_class.Get()); + // Static field in another class. + EXPECT_TRUE(strict_transaction.WriteConstraint(soa.Self(), boolean_class.Get(), true_field)); + EXPECT_TRUE(strict_transaction.ReadConstraint(soa.Self(), boolean_class.Get(), true_field)); + // Instance field in another class. Do not check ReadConstraint(), it expects only static fields. + EXPECT_FALSE(strict_transaction.WriteConstraint(soa.Self(), true_value.Get(), value_field)); + // Static field in the same class. + EXPECT_FALSE(strict_transaction.WriteConstraint(soa.Self(), static_field_class.Get(), int_field)); + EXPECT_FALSE(strict_transaction.ReadConstraint(soa.Self(), static_field_class.Get(), int_field)); +} + } // namespace art |