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