Update Transaction for boot image extension.
And clean up transaction-related code to keep test code
out of the production binaries.
Test: Add TransactionTest#Constraints to transaction_test.
Test: m test-art-host-gtest
Test: testrunner.py --host
Test: aosp_taimen-userdebug boots.
Change-Id: Iefe5f1cfde95f564069249148f9e7d71564d7a10
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index c548fdd..ff0f891 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -254,6 +254,10 @@
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 c151306..79b6c26 100644
--- a/runtime/aot_class_linker.cc
+++ b/runtime/aot_class_linker.cc
@@ -72,7 +72,7 @@
}
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 05ec9e6..135dc7b 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -433,6 +433,43 @@
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 3595c73..e1ab3df 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -168,6 +168,16 @@
// 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 fbcee39..0d74dbb 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -137,29 +137,6 @@
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 9953743..e017b17 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -109,7 +109,7 @@
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 @@
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 @@
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 495039c..4429f63 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -785,19 +785,19 @@
{
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 @@
{
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 @@
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 @@
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 @@
CHECK(name_string != nullptr);
if (in_transaction) {
- Runtime::Current()->EnterTransactionMode();
+ EnterTransactionMode();
}
CHECK(!self->IsExceptionPending());
@@ -1132,13 +1132,13 @@
} 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 91a3c45..0b4b43b 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2381,18 +2381,14 @@
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 9dd7493..a276b87 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -500,7 +500,6 @@
// 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 c534a42..9a51e0f 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 @@
// 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 @@
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 de6edd2..fa874b8 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 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 @@
// 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 @@
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 @@
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 7ad741a..ff93dc8 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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, 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