Fix Transaction constraint validation...

... for boot image extensions. Add WriteConstraint checks
to APUT instructions and add necessary WriteConstraint and
WriteValueConstraint checks to UnstartedRuntime.

For strict transactions (app compilation), prevent writing
to boot image objects. However, more work is required for
this use case as the UnstartedRuntime needs a review for
missing ReadConstraint checks and the WriteValueConstraint
may need to be more restrictive.

While the transaction_test is improved to test Transaction
constraints more thoroughly, no regression tests are
provided for the previously missing checks. Such tests are
difficult to write as they would require compilation of
a custom boot image.

Test: Manual; include java.lang.Locale[] in primary boot
      image by patching CompilerDriver::LoadImageClasses(),
          +  if (GetCompilerOptions().IsBootImage()) {
          +    image_classes->insert("[Ljava/util/Locale;");
          +  }
      , and build. This previously aborted in ImageWriter:
          Image object without assigned bin slot: \
          java.util.concurrent.ConcurrentHashMap$Node
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 119800099
Bug: 147596904
Change-Id: Ibfe1b24b10dbd982b4e4ae4d98289e587a842812
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index e4aef8e..d582cec 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -2626,6 +2626,7 @@
 
     if (compiler_options_->IsForceDeterminism()) {
       // To make identity hashcode deterministic, set a known seed.
+      // TODO: Select a different seed for boot image extensions; maybe hash dex file checksums.
       mirror::Object::SetHashCodeSeed(987654321U);
     }
 
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 0facc85..fb1e69d 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -2152,6 +2152,7 @@
     }
   }
 
+  std::vector<mirror::Object*> missed_objects;
   auto ensure_bin_slots_assigned = [&](mirror::Object* obj)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     if (!image_writer_->IsInBootImage(obj)) {
@@ -2188,12 +2189,24 @@
             return;
           }
         }
-        LOG(FATAL) << "Image object without assigned bin slot: "
-            << mirror::Object::PrettyTypeOf(obj) << " " << obj;
+        missed_objects.push_back(obj);
       }
     }
   };
   Runtime::Current()->GetHeap()->VisitObjects(ensure_bin_slots_assigned);
+  if (!missed_objects.empty()) {
+    const gc::Verification* v = Runtime::Current()->GetHeap()->GetVerification();
+    size_t num_missed_objects = missed_objects.size();
+    size_t num_paths = std::min<size_t>(num_missed_objects, 5u);  // Do not flood the output.
+    ArrayRef<mirror::Object*> missed_objects_head =
+        ArrayRef<mirror::Object*>(missed_objects).SubArray(/*pos=*/ 0u, /*length=*/ num_paths);
+    for (mirror::Object* obj : missed_objects_head) {
+      LOG(ERROR) << "Image object without assigned bin slot: "
+          << mirror::Object::PrettyTypeOf(obj) << " " << obj
+          << " " << v->FirstPathFromRootSet(obj);
+    }
+    LOG(FATAL) << "Found " << num_missed_objects << " objects without assigned bin slots.";
+  }
 }
 
 void ImageWriter::LayoutHelper::FinalizeBinSlotOffsets() {
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index d7fee02..c6d8569 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -520,7 +520,7 @@
   if (is_static) {
     obj = f->GetDeclaringClass();
     if (transaction_active) {
-      if (Runtime::Current()->GetTransaction()->ReadConstraint(self, obj, f)) {
+      if (Runtime::Current()->GetTransaction()->ReadConstraint(self, obj)) {
         Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Can't read static fields of "
             + obj->PrettyTypeOf() + " since it does not belong to clinit's class.");
         return false;
@@ -635,6 +635,20 @@
   return true;
 }
 
+static inline bool CheckWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  Runtime* runtime = Runtime::Current();
+  if (runtime->GetTransaction()->WriteConstraint(self, obj)) {
+    DCHECK(runtime->GetHeap()->ObjectIsInBootImageSpace(obj) || obj->IsClass());
+    const char* base_msg = runtime->GetHeap()->ObjectIsInBootImageSpace(obj)
+        ? "Can't set fields of boot image "
+        : "Can't set fields of ";
+    runtime->AbortTransactionAndThrowAbortError(self, base_msg + obj->PrettyTypeOf());
+    return false;
+  }
+  return true;
+}
+
 static inline bool CheckWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   Runtime* runtime = Runtime::Current();
@@ -676,21 +690,8 @@
       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;
-    }
+  if (transaction_active && !CheckWriteConstraint(self, obj)) {
+    return false;
   }
 
   uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h
index 989f997..863612f 100644
--- a/runtime/interpreter/interpreter_switch_impl-inl.h
+++ b/runtime/interpreter/interpreter_switch_impl-inl.h
@@ -343,6 +343,9 @@
     if (UNLIKELY(!array->CheckIsValidIndex(index))) {
       return false;  // Pending exception.
     } else {
+      if (transaction_active && !CheckWriteConstraint(self, array)) {
+        return false;
+      }
       array->template SetWithoutChecks<transaction_active>(index, value);
     }
     return true;
@@ -974,7 +977,8 @@
     ObjPtr<mirror::Object> val = GetVRegReference(A());
     ObjPtr<mirror::ObjectArray<mirror::Object>> array = a->AsObjectArray<mirror::Object>();
     if (array->CheckIsValidIndex(index) && array->CheckAssignable(val)) {
-      if (transaction_active && !CheckWriteValueConstraint(self, val)) {
+      if (transaction_active &&
+          (!CheckWriteConstraint(self, array) || !CheckWriteValueConstraint(self, val))) {
         return false;
       }
       array->SetWithoutChecks<transaction_active>(index, val);
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 9bb3e9e..5986982 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -839,6 +839,11 @@
     return;
   }
 
+  if (Runtime::Current()->IsActiveTransaction() && !CheckWriteConstraint(self, dst_obj)) {
+    DCHECK(self->IsExceptionPending());
+    return;
+  }
+
   // Type checking.
   ObjPtr<mirror::Class> src_type = shadow_frame->GetVRegReference(arg_offset)->GetClass()->
       GetComponentType();
@@ -1461,6 +1466,10 @@
   bool success;
   // Check whether we're in a transaction, call accordingly.
   if (Runtime::Current()->IsActiveTransaction()) {
+    if (!CheckWriteConstraint(self, obj)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
     success = obj->CasFieldStrongSequentiallyConsistent64<true>(MemberOffset(offset),
                                                                 expectedValue,
                                                                 newValue);
@@ -1482,7 +1491,7 @@
   }
   int64_t offset = shadow_frame->GetVRegLong(arg_offset + 2);
   mirror::Object* expected_value = shadow_frame->GetVRegReference(arg_offset + 4);
-  mirror::Object* newValue = shadow_frame->GetVRegReference(arg_offset + 5);
+  mirror::Object* new_value = shadow_frame->GetVRegReference(arg_offset + 5);
 
   // Must use non transactional mode.
   if (kUseReadBarrier) {
@@ -1503,15 +1512,19 @@
   bool success;
   // Check whether we're in a transaction, call accordingly.
   if (Runtime::Current()->IsActiveTransaction()) {
+    if (!CheckWriteConstraint(self, obj) || !CheckWriteValueConstraint(self, new_value)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
     success = obj->CasFieldObject<true>(MemberOffset(offset),
                                         expected_value,
-                                        newValue,
+                                        new_value,
                                         CASMode::kStrong,
                                         std::memory_order_seq_cst);
   } else {
     success = obj->CasFieldObject<false>(MemberOffset(offset),
                                          expected_value,
-                                         newValue,
+                                         new_value,
                                          CASMode::kStrong,
                                          std::memory_order_seq_cst);
   }
@@ -1544,6 +1557,10 @@
   int64_t offset = shadow_frame->GetVRegLong(arg_offset + 2);
   mirror::Object* value = shadow_frame->GetVRegReference(arg_offset + 4);
   if (Runtime::Current()->IsActiveTransaction()) {
+    if (!CheckWriteConstraint(self, obj) || !CheckWriteValueConstraint(self, value)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
     obj->SetFieldObjectVolatile<true>(MemberOffset(offset), value);
   } else {
     obj->SetFieldObjectVolatile<false>(MemberOffset(offset), value);
@@ -1560,12 +1577,16 @@
     return;
   }
   int64_t offset = shadow_frame->GetVRegLong(arg_offset + 2);
-  mirror::Object* newValue = shadow_frame->GetVRegReference(arg_offset + 4);
+  mirror::Object* new_value = shadow_frame->GetVRegReference(arg_offset + 4);
   std::atomic_thread_fence(std::memory_order_release);
   if (Runtime::Current()->IsActiveTransaction()) {
-    obj->SetFieldObject<true>(MemberOffset(offset), newValue);
+    if (!CheckWriteConstraint(self, obj) || !CheckWriteValueConstraint(self, new_value)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
+    obj->SetFieldObject<true>(MemberOffset(offset), new_value);
   } else {
-    obj->SetFieldObject<false>(MemberOffset(offset), newValue);
+    obj->SetFieldObject<false>(MemberOffset(offset), new_value);
   }
 }
 
@@ -1893,6 +1914,10 @@
   jint newValue = args[4];
   bool success;
   if (Runtime::Current()->IsActiveTransaction()) {
+    if (!CheckWriteConstraint(self, obj)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
     success = obj->CasField32<true>(MemberOffset(offset),
                                     expectedValue,
                                     newValue,
@@ -1934,11 +1959,15 @@
     return;
   }
   jlong offset = (static_cast<uint64_t>(args[2]) << 32) | args[1];
-  ObjPtr<mirror::Object> newValue = reinterpret_cast32<mirror::Object*>(args[3]);
+  ObjPtr<mirror::Object> new_value = reinterpret_cast32<mirror::Object*>(args[3]);
   if (Runtime::Current()->IsActiveTransaction()) {
-    obj->SetFieldObject<true>(MemberOffset(offset), newValue);
+    if (!CheckWriteConstraint(self, obj) || !CheckWriteValueConstraint(self, new_value)) {
+      DCHECK(self->IsExceptionPending());
+      return;
+    }
+    obj->SetFieldObject<true>(MemberOffset(offset), new_value);
   } else {
-    obj->SetFieldObject<false>(MemberOffset(offset), newValue);
+    obj->SetFieldObject<false>(MemberOffset(offset), new_value);
   }
 }
 
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 46bbea3..6756c7b 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -43,9 +43,9 @@
     : log_lock_("transaction log lock", kTransactionLogLock),
       aborted_(false),
       rolling_back_(false),
-      heap_(strict ? nullptr : Runtime::Current()->GetHeap()),
+      heap_(Runtime::Current()->GetHeap()),
+      strict_(strict),
       root_(root) {
-  DCHECK_EQ(strict, IsStrict());
   DCHECK(Runtime::Current()->IsAotCompiler());
 }
 
@@ -117,16 +117,20 @@
   return abort_message_;
 }
 
-bool Transaction::WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) {
+bool Transaction::WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) {
+  DCHECK(obj != nullptr);
   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);
+
+  // Prevent changes in boot image spaces for app or boot image extension.
+  // For boot image there are no boot image spaces and this condition evaluates to false.
+  if (heap_->ObjectIsInBootImageSpace(obj)) {
+    return true;
   }
+
+  // For apps, also prevent writing to other classes.
+  return IsStrict() &&
+         obj->IsClass() &&  // no constraint updating instances or arrays
+         obj != root_;  // modifying other classes' static field, fail
 }
 
 bool Transaction::WriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) {
@@ -147,8 +151,9 @@
   }
 }
 
-bool Transaction::ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) {
-  DCHECK(field->IsStatic());
+bool Transaction::ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj) {
+  // Read constraints are checked only for static field reads as there are
+  // no constraints on reading instance fields and array elements.
   DCHECK(obj->IsClass());
   MutexLock mu(self, log_lock_);
   if (IsStrict()) {
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 2dc7c94..f05bfee 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -67,7 +67,7 @@
   // one class's clinit will not be allowed to read or modify another class's static fields, unless
   // the transaction is aborted.
   bool IsStrict() {
-    return heap_ == nullptr;
+    return strict_;
   }
 
   // Record object field changes.
@@ -140,11 +140,11 @@
       REQUIRES(!log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field)
+  bool ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj)
       REQUIRES(!log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field)
+  bool WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj)
       REQUIRES(!log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -317,6 +317,7 @@
   bool aborted_ GUARDED_BY(log_lock_);
   bool rolling_back_;  // Single thread, no race.
   gc::Heap* const heap_;
+  const bool strict_;
   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 c87c438..74725c2 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -606,18 +606,22 @@
 
 TEST_F(TransactionTest, Constraints) {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<5> hs(soa.Self());
+  StackHandleScope<11> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader(
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("Transaction"))));
 
+  gc::Heap* heap = Runtime::Current()->GetHeap();
   Handle<mirror::Class> boolean_class = hs.NewHandle(
       class_linker_->FindClass(soa.Self(), "Ljava/lang/Boolean;", class_loader));
+  ASSERT_TRUE(boolean_class != nullptr);
+  ASSERT_TRUE(heap->ObjectIsInBootImageSpace(boolean_class.Get()));
   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);
+  ASSERT_TRUE(heap->ObjectIsInBootImageSpace(true_value.Get()));
   ArtField* value_field =
       mirror::Class::FindField(soa.Self(), boolean_class.Get(), "value", "Z");
   ASSERT_TRUE(value_field != nullptr);
@@ -625,50 +629,112 @@
 
   Handle<mirror::Class> static_field_class(hs.NewHandle(
       class_linker_->FindClass(soa.Self(), "LTransaction$StaticFieldClass;", class_loader)));
+  ASSERT_TRUE(static_field_class != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(static_field_class.Get()));
   ArtField* int_field =
       mirror::Class::FindField(soa.Self(), static_field_class.Get(), "intField", "I");
   ASSERT_TRUE(int_field != nullptr);
 
-  Handle<mirror::Class> long_array_dim2 = hs.NewHandle(
+  Handle<mirror::Class> static_fields_test_class(hs.NewHandle(
+      class_linker_->FindClass(soa.Self(), "LStaticFieldsTest;", class_loader)));
+  ASSERT_TRUE(static_fields_test_class != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(static_fields_test_class.Get()));
+  ArtField* static_fields_test_int_field =
+      mirror::Class::FindField(soa.Self(), static_fields_test_class.Get(), "intField", "I");
+  ASSERT_TRUE(static_fields_test_int_field != nullptr);
+
+  Handle<mirror::Class> instance_fields_test_class(hs.NewHandle(
+      class_linker_->FindClass(soa.Self(), "LInstanceFieldsTest;", class_loader)));
+  ASSERT_TRUE(instance_fields_test_class != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(instance_fields_test_class.Get()));
+  ArtField* instance_fields_test_int_field =
+      mirror::Class::FindField(soa.Self(), instance_fields_test_class.Get(), "intField", "I");
+  ASSERT_TRUE(instance_fields_test_int_field != nullptr);
+  Handle<mirror::Object> instance_fields_test_object = hs.NewHandle(
+      instance_fields_test_class->Alloc(soa.Self(), heap->GetCurrentAllocator()));
+  ASSERT_TRUE(instance_fields_test_object != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(instance_fields_test_object.Get()));
+
+  Handle<mirror::Class> long_array_dim2_class = hs.NewHandle(
       class_linker_->FindClass(soa.Self(), "[[J", class_loader));
+  ASSERT_TRUE(long_array_dim2_class != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(long_array_dim2_class.Get()));
+  ASSERT_TRUE(heap->ObjectIsInBootImageSpace(long_array_dim2_class->GetComponentType()));
+  Handle<mirror::Array> long_array_dim2 = hs.NewHandle(mirror::Array::Alloc(
+      soa.Self(),
+      long_array_dim2_class.Get(),
+      /*component_count=*/ 1,
+      long_array_dim2_class->GetComponentSizeShift(),
+      heap->GetCurrentAllocator()));
   ASSERT_TRUE(long_array_dim2 != nullptr);
-  gc::Heap* heap = Runtime::Current()->GetHeap();
   ASSERT_FALSE(heap->ObjectIsInBootImageSpace(long_array_dim2.Get()));
-  ASSERT_TRUE(heap->ObjectIsInBootImageSpace(long_array_dim2->GetComponentType()));
+  Handle<mirror::Array> long_array = hs.NewHandle(mirror::Array::Alloc(
+      soa.Self(),
+      long_array_dim2_class->GetComponentType(),
+      /*component_count=*/ 1,
+      long_array_dim2_class->GetComponentType()->GetComponentSizeShift(),
+      heap->GetCurrentAllocator()));
+  ASSERT_TRUE(long_array != nullptr);
+  ASSERT_FALSE(heap->ObjectIsInBootImageSpace(long_array.Get()));
+
+  // Use the Array's IfTable as an array from the boot image.
+  Handle<mirror::ObjectArray<mirror::Object>> array_iftable =
+      hs.NewHandle(long_array_dim2_class->GetIfTable());
+  ASSERT_TRUE(array_iftable != nullptr);
+  ASSERT_TRUE(heap->ObjectIsInBootImageSpace(array_iftable.Get()));
 
   // Test non-strict transaction.
   Transaction transaction(/*strict=*/ false, /*root=*/ nullptr);
   // Static field in boot image.
-  ASSERT_TRUE(heap->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(heap->ObjectIsInBootImageSpace(true_value.Get()));
-  EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), true_value.Get(), value_field));
+  EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), boolean_class.Get()));
+  EXPECT_FALSE(transaction.ReadConstraint(soa.Self(), boolean_class.Get()));
+  // Instance field or array element in boot image.
+  // Do not check ReadConstraint(), it expects only static fields (checks for class object).
+  EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), true_value.Get()));
+  EXPECT_TRUE(transaction.WriteConstraint(soa.Self(), array_iftable.Get()));
   // Static field not in boot image.
-  ASSERT_FALSE(heap->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));
+  EXPECT_FALSE(transaction.WriteConstraint(soa.Self(), static_fields_test_class.Get()));
+  EXPECT_FALSE(transaction.ReadConstraint(soa.Self(), static_fields_test_class.Get()));
+  // Instance field or array element not in boot image.
+  // Do not check ReadConstraint(), it expects only static fields (checks for class object).
+  EXPECT_FALSE(transaction.WriteConstraint(soa.Self(), instance_fields_test_object.Get()));
+  EXPECT_FALSE(transaction.WriteConstraint(soa.Self(), long_array_dim2.Get()));
   // Write value constraints.
-  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), static_field_class.Get()));
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), static_fields_test_class.Get()));
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), instance_fields_test_object.Get()));
+  EXPECT_TRUE(transaction.WriteValueConstraint(soa.Self(), long_array_dim2->GetClass()));
   EXPECT_TRUE(transaction.WriteValueConstraint(soa.Self(), long_array_dim2.Get()));
-  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), long_array_dim2->GetComponentType()));
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), long_array->GetClass()));
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), long_array.Get()));
 
   // 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 boot image.
+  EXPECT_TRUE(strict_transaction.WriteConstraint(soa.Self(), boolean_class.Get()));
+  EXPECT_TRUE(strict_transaction.ReadConstraint(soa.Self(), boolean_class.Get()));
+  // Instance field or array element in boot image.
+  // Do not check ReadConstraint(), it expects only static fields (checks for class object).
+  EXPECT_TRUE(strict_transaction.WriteConstraint(soa.Self(), true_value.Get()));
+  EXPECT_TRUE(strict_transaction.WriteConstraint(soa.Self(), array_iftable.Get()));
+  // Static field in another class not in boot image.
+  EXPECT_TRUE(strict_transaction.WriteConstraint(soa.Self(), static_fields_test_class.Get()));
+  EXPECT_TRUE(strict_transaction.ReadConstraint(soa.Self(), static_fields_test_class.Get()));
+  // Instance field or array element not in boot image.
+  // Do not check ReadConstraint(), it expects only static fields (checks for class object).
+  EXPECT_FALSE(strict_transaction.WriteConstraint(soa.Self(), instance_fields_test_object.Get()));
+  EXPECT_FALSE(strict_transaction.WriteConstraint(soa.Self(), long_array_dim2.Get()));
   // 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));
+  EXPECT_FALSE(strict_transaction.WriteConstraint(soa.Self(), static_field_class.Get()));
+  EXPECT_FALSE(strict_transaction.ReadConstraint(soa.Self(), static_field_class.Get()));
   // Write value constraints.
-  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), static_field_class.Get()));
-  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), long_array_dim2.Get()));
+  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), static_fields_test_class.Get()));
   EXPECT_FALSE(
-      strict_transaction.WriteValueConstraint(soa.Self(), long_array_dim2->GetComponentType()));
+      strict_transaction.WriteValueConstraint(soa.Self(), instance_fields_test_object.Get()));
+  // TODO: The following may be revised, see a TODO in Transaction::WriteValueConstraint().
+  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), long_array_dim2->GetClass()));
+  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), long_array_dim2.Get()));
+  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), long_array->GetClass()));
+  EXPECT_FALSE(strict_transaction.WriteValueConstraint(soa.Self(), long_array.Get()));
 }
 
 }  // namespace art