Check stored references in transactional interpreter.

For boot image extension, we need to reject references to
classes that are defined in a dex file belonging to the boot
image we're compiling against but not actually in the boot
image. Allowing such references could yield duplicate class
objects from multiple extensions.

Test: Additional tests in transaction_test.
Test: m test-art-host-gtest
Test: testrunner.py --host --interp-ac
Change-Id: I99eaec331f6d992dba60aeb98a88c268edac11ac
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 9a51e0f..47e59a9 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -128,6 +128,42 @@
   }
 }
 
+bool Transaction::WriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) {
+  if (value == nullptr) {
+    return false;  // We can always store null values.
+  }
+  gc::Heap* heap = Runtime::Current()->GetHeap();
+  MutexLock mu(self, log_lock_);
+  if (IsStrict()) {
+    // TODO: Should we restrict writes the same way as for boot image extension?
+    return false;
+  } else if (heap->GetBootImageSpaces().empty()) {
+    return false;  // No constraints for boot image.
+  } else {
+    // Boot image extension.
+    // Do not allow storing references to a class or instances of a class defined in a dex file
+    // belonging to the boot image we're compiling against but not itself in the boot image.
+    // Allowing this could yield duplicate class objects from multiple extensions.
+    if (heap->ObjectIsInBootImageSpace(value)) {
+      return false;  // References to boot image objects are OK.
+    }
+    ObjPtr<mirror::Class> klass = value->IsClass() ? value->AsClass() : value->GetClass();
+    if (!value->IsClass() && heap->ObjectIsInBootImageSpace(klass)) {
+      // Instances of boot image classes are OK.
+      DCHECK(klass->IsInitialized());
+      return false;
+    }
+    // For arrays we need to determine the dex file based on the element type.
+    while (klass->IsArrayClass()) {
+      klass = klass->GetComponentType();
+    }
+    if (klass->IsPrimitive() || heap->ObjectIsInBootImageSpace(klass->GetDexCache())) {
+      return true;  // Boot image dex file but not boot image `klass`.
+    }
+    return false;
+  }
+}
+
 bool Transaction::ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj, ArtField* field) {
   DCHECK(field->IsStatic());
   DCHECK(obj->IsClass());