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/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 657f0ac..9fccde9 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -632,6 +632,20 @@
   return true;
 }
 
+static inline bool CheckWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  Runtime* runtime = Runtime::Current();
+  if (runtime->GetTransaction()->WriteValueConstraint(self, value)) {
+    DCHECK(value != nullptr);
+    std::string msg = value->IsClass()
+        ? "Can't store reference to class " + value->AsClass()->PrettyDescriptor()
+        : "Can't store reference to instance of " + value->GetClass()->PrettyDescriptor();
+    runtime->AbortTransactionAndThrowAbortError(self, msg);
+    return false;
+  }
+  return true;
+}
+
 // Handles iput-XXX and sput-XXX instructions.
 // Returns true on success, otherwise throws an exception and returns false.
 template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
@@ -678,6 +692,13 @@
 
   uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
   JValue value = GetFieldValue<field_type>(shadow_frame, vregA);
+
+  if (transaction_active &&
+      field_type == Primitive::kPrimNot &&
+      !CheckWriteValueConstraint(self, value.GetL())) {
+    return false;
+  }
+
   return DoFieldPutCommon<field_type, do_assignability_check, transaction_active>(self,
                                                                                   shadow_frame,
                                                                                   obj,
diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h
index 8afaed5..989f997 100644
--- a/runtime/interpreter/interpreter_switch_impl-inl.h
+++ b/runtime/interpreter/interpreter_switch_impl-inl.h
@@ -974,6 +974,9 @@
     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)) {
+        return false;
+      }
       array->SetWithoutChecks<transaction_active>(index, val);
     } else {
       return false;  // Pending exception.
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());
diff --git a/runtime/transaction.h b/runtime/transaction.h
index fa874b8..2dc7c94 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -148,6 +148,10 @@
       REQUIRES(!log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  bool WriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
+      REQUIRES(!log_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   class ObjectLog : public ValueObject {
    public:
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index ff93dc8..c87c438 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -606,7 +606,7 @@
 
 TEST_F(TransactionTest, Constraints) {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<4> hs(soa.Self());
+  StackHandleScope<5> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader(
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("Transaction"))));
 
@@ -629,19 +629,30 @@
       mirror::Class::FindField(soa.Self(), static_field_class.Get(), "intField", "I");
   ASSERT_TRUE(int_field != nullptr);
 
+  Handle<mirror::Class> long_array_dim2 = hs.NewHandle(
+      class_linker_->FindClass(soa.Self(), "[[J", class_loader));
+  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()));
+
   // Test non-strict transaction.
   Transaction transaction(/*strict=*/ false, /*root=*/ nullptr);
   // Static field in boot image.
-  ASSERT_TRUE(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(boolean_class.Get()));
+  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(Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(true_value.Get()));
+  ASSERT_TRUE(heap->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()));
+  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));
+  // Write value constraints.
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), static_field_class.Get()));
+  EXPECT_TRUE(transaction.WriteValueConstraint(soa.Self(), long_array_dim2.Get()));
+  EXPECT_FALSE(transaction.WriteValueConstraint(soa.Self(), long_array_dim2->GetComponentType()));
 
   // Test strict transaction.
   Transaction strict_transaction(/*strict=*/ true, /*root=*/ static_field_class.Get());
@@ -653,6 +664,11 @@
   // 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));
+  // 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(), long_array_dim2->GetComponentType()));
 }
 
 }  // namespace art