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