Merge "ART: Allow array-ness for unresolved merge types"
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 537d9c9..9bcbdbd 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2425,6 +2425,10 @@
if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with array type "
<< array_type;
+ } else if (array_type.IsUnresolvedTypes()) {
+ // If it's an unresolved array type, it must be non-primitive.
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data for array of type "
+ << array_type;
} else {
const RegType& component_type = reg_types_.GetComponentType(array_type, GetClassLoader());
DCHECK(!component_type.IsConflict());
@@ -4209,6 +4213,7 @@
const RegType& precise_type = reg_types_.FromUninitialized(res_type);
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_22c(), precise_type);
} else {
+ DCHECK(!res_type.IsUnresolvedMergedReference());
// Verify each register. If "arg_count" is bad, VerifyRegisterType() will run off the end of
// the list and fail. It's legal, if silly, for arg_count to be zero.
const RegType& expected_type = reg_types_.GetComponentType(res_type, GetClassLoader());
@@ -4253,6 +4258,15 @@
}
} else if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "not array type " << array_type << " with aget";
+ } else if (array_type.IsUnresolvedTypes()) {
+ // Unresolved array types must be reference array types.
+ if (is_primitive) {
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "reference array type " << array_type
+ << " source for category 1 aget";
+ } else {
+ Fail(VERIFY_ERROR_NO_CLASS) << "cannot verify aget for " << array_type
+ << " because of missing class";
+ }
} else {
/* verify the class */
const RegType& component_type = reg_types_.GetComponentType(array_type, GetClassLoader());
@@ -4363,6 +4377,15 @@
work_line_->VerifyRegisterType(this, inst->VRegA_23x(), *modified_reg_type);
} else if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "not array type " << array_type << " with aput";
+ } else if (array_type.IsUnresolvedTypes()) {
+ // Unresolved array types must be reference array types.
+ if (is_primitive) {
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "put insn has type '" << insn_type
+ << "' but unresolved type '" << array_type << "'";
+ } else {
+ Fail(VERIFY_ERROR_NO_CLASS) << "cannot verify aput for " << array_type
+ << " because of missing class";
+ }
} else {
const RegType& component_type = reg_types_.GetComponentType(array_type, GetClassLoader());
const uint32_t vregA = inst->VRegA_23x();
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 0894f5d..bd0308b 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -517,9 +517,21 @@
}
}
+bool RegType::IsJavaLangObject() const SHARED_REQUIRES(Locks::mutator_lock_) {
+ return IsReference() && GetClass()->IsObjectClass();
+}
+
bool RegType::IsObjectArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_) {
- if (IsUnresolvedTypes() && !IsUnresolvedMergedReference() && !IsUnresolvedSuperClass()) {
- // Primitive arrays will always resolve
+ if (IsUnresolvedTypes()) {
+ DCHECK(!IsUnresolvedMergedReference());
+
+ if (IsUnresolvedSuperClass()) {
+ // Cannot be an array, as the superclass of arrays is java.lang.Object (which cannot be
+ // unresolved).
+ return false;
+ }
+
+ // Primitive arrays will always resolve.
DCHECK(descriptor_[1] == 'L' || descriptor_[1] == '[');
return descriptor_[0] == '[';
} else if (HasClass()) {
@@ -530,12 +542,15 @@
}
}
-bool RegType::IsJavaLangObject() const SHARED_REQUIRES(Locks::mutator_lock_) {
- return IsReference() && GetClass()->IsObjectClass();
-}
-
bool RegType::IsArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_) {
- if (IsUnresolvedTypes() && !IsUnresolvedMergedReference() && !IsUnresolvedSuperClass()) {
+ if (IsUnresolvedTypes()) {
+ DCHECK(!IsUnresolvedMergedReference());
+
+ if (IsUnresolvedSuperClass()) {
+ // Cannot be an array, as the superclass of arrays is java.lang.Object (which cannot be
+ // unresolved).
+ return false;
+ }
return descriptor_[0] == '[';
} else if (HasClass()) {
return GetClass()->IsArrayClass();
@@ -793,11 +808,50 @@
}
}
void UnresolvedMergedType::CheckInvariants() const {
+ CHECK(reg_type_cache_ != nullptr);
+
// Unresolved merged types: merged types should be defined.
CHECK(descriptor_.empty()) << *this;
CHECK(klass_.IsNull()) << *this;
+
+ CHECK(!resolved_part_.IsConflict());
CHECK(resolved_part_.IsReferenceTypes());
CHECK(!resolved_part_.IsUnresolvedTypes());
+
+ CHECK(resolved_part_.IsZero() ||
+ !(resolved_part_.IsArrayTypes() && !resolved_part_.IsObjectArrayTypes()));
+
+ CHECK_GT(unresolved_types_.NumSetBits(), 0U);
+ bool unresolved_is_array =
+ reg_type_cache_->GetFromId(unresolved_types_.GetHighestBitSet()).IsArrayTypes();
+ for (uint32_t idx : unresolved_types_.Indexes()) {
+ const RegType& t = reg_type_cache_->GetFromId(idx);
+ CHECK_EQ(unresolved_is_array, t.IsArrayTypes());
+ }
+
+ if (!resolved_part_.IsZero()) {
+ CHECK_EQ(resolved_part_.IsArrayTypes(), unresolved_is_array);
+ }
+}
+
+bool UnresolvedMergedType::IsArrayTypes() const {
+ // For a merge to be an array, both the resolved and the unresolved part need to be object
+ // arrays.
+ // (Note: we encode a missing resolved part [which doesn't need to be an array] as zero.)
+
+ if (!resolved_part_.IsZero() && !resolved_part_.IsArrayTypes()) {
+ return false;
+ }
+
+ // It is enough to check just one of the merged types. Otherwise the merge should have been
+ // collapsed (checked in CheckInvariants on construction).
+ uint32_t idx = unresolved_types_.GetHighestBitSet();
+ const RegType& unresolved = reg_type_cache_->GetFromId(idx);
+ return unresolved.IsArrayTypes();
+}
+bool UnresolvedMergedType::IsObjectArrayTypes() const {
+ // Same as IsArrayTypes, as primitive arrays are always resolved.
+ return IsArrayTypes();
}
void UnresolvedReferenceType::CheckInvariants() const {
@@ -824,6 +878,14 @@
return false;
}
+ if (IsUnresolvedTypes() || src.IsUnresolvedTypes()) {
+ // An unresolved array type means that it's an array of some reference type. Reference arrays
+ // can never be assigned to primitive-type arrays, and vice versa. So it is a soft error if
+ // both arrays are reference arrays, otherwise a hard error.
+ *soft_error = IsObjectArrayTypes() && src.IsObjectArrayTypes();
+ return false;
+ }
+
const RegType& cmp1 = reg_types.GetComponentType(*this, class_loader.Get());
const RegType& cmp2 = reg_types.GetComponentType(src, class_loader.Get());
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index 7c7981e..4837490 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -172,8 +172,8 @@
}
virtual bool HasClassVirtual() const { return false; }
bool IsJavaLangObject() const SHARED_REQUIRES(Locks::mutator_lock_);
- bool IsArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_);
- bool IsObjectArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_);
+ virtual bool IsArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_);
+ virtual bool IsObjectArrayTypes() const SHARED_REQUIRES(Locks::mutator_lock_);
Primitive::Type GetPrimitiveType() const;
bool IsJavaLangObjectArray() const
SHARED_REQUIRES(Locks::mutator_lock_);
@@ -905,6 +905,9 @@
bool IsUnresolvedTypes() const OVERRIDE { return true; }
+ bool IsArrayTypes() const OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
+ bool IsObjectArrayTypes() const OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
+
std::string Dump() const OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
private:
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index b171b75..208ef51 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -347,29 +347,39 @@
kDefaultArenaBitVectorBytes * kBitsPerByte, // Allocate at least 8 bytes.
true); // Is expandable.
const RegType* left_resolved;
+ bool left_unresolved_is_array;
if (left.IsUnresolvedMergedReference()) {
- const UnresolvedMergedType* left_merge = down_cast<const UnresolvedMergedType*>(&left);
- types.Copy(&left_merge->GetUnresolvedTypes());
- left_resolved = &left_merge->GetResolvedPart();
+ const UnresolvedMergedType& left_merge = *down_cast<const UnresolvedMergedType*>(&left);
+
+ types.Copy(&left_merge.GetUnresolvedTypes());
+ left_resolved = &left_merge.GetResolvedPart();
+ left_unresolved_is_array = left.IsArrayTypes();
} else if (left.IsUnresolvedTypes()) {
types.ClearAllBits();
types.SetBit(left.GetId());
left_resolved = &Zero();
+ left_unresolved_is_array = left.IsArrayTypes();
} else {
types.ClearAllBits();
left_resolved = &left;
+ left_unresolved_is_array = false;
}
const RegType* right_resolved;
+ bool right_unresolved_is_array;
if (right.IsUnresolvedMergedReference()) {
- const UnresolvedMergedType* right_merge = down_cast<const UnresolvedMergedType*>(&right);
- types.Union(&right_merge->GetUnresolvedTypes());
- right_resolved = &right_merge->GetResolvedPart();
+ const UnresolvedMergedType& right_merge = *down_cast<const UnresolvedMergedType*>(&right);
+
+ types.Union(&right_merge.GetUnresolvedTypes());
+ right_resolved = &right_merge.GetResolvedPart();
+ right_unresolved_is_array = right.IsArrayTypes();
} else if (right.IsUnresolvedTypes()) {
types.SetBit(right.GetId());
right_resolved = &Zero();
+ right_unresolved_is_array = right.IsArrayTypes();
} else {
right_resolved = &right;
+ right_unresolved_is_array = false;
}
// Merge the resolved parts. Left and right might be equal, so use SafeMerge.
@@ -379,6 +389,23 @@
return Conflict();
}
+ bool resolved_merged_is_array = resolved_parts_merged.IsArrayTypes();
+ if (left_unresolved_is_array || right_unresolved_is_array || resolved_merged_is_array) {
+ // Arrays involved, see if we need to merge to Object.
+
+ // Is the resolved part a primitive array?
+ if (resolved_merged_is_array && !resolved_parts_merged.IsObjectArrayTypes()) {
+ return JavaLangObject(false /* precise */);
+ }
+
+ // Is any part not an array (but exists)?
+ if ((!left_unresolved_is_array && left_resolved != &left) ||
+ (!right_unresolved_is_array && right_resolved != &right) ||
+ !resolved_merged_is_array) {
+ return JavaLangObject(false /* precise */);
+ }
+ }
+
// Check if entry already exists.
for (size_t i = primitive_count_; i < entries_.size(); i++) {
const RegType* cur_entry = entries_[i];
@@ -584,6 +611,7 @@
if (!array.IsArrayTypes()) {
return Conflict();
} else if (array.IsUnresolvedTypes()) {
+ DCHECK(!array.IsUnresolvedTypes()); // Caller must make sure not to ask for this.
const std::string descriptor(array.GetDescriptor().as_string());
return FromDescriptor(loader, descriptor.c_str() + 1, false);
} else {
diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt
index 8808a50..5a3857d 100644
--- a/test/800-smali/expected.txt
+++ b/test/800-smali/expected.txt
@@ -60,4 +60,9 @@
b/26594149 (8)
b/27148248
b/26965384
+b/27799205 (1)
+b/27799205 (2)
+b/27799205 (3)
+b/27799205 (4)
+b/27799205 (5)
Done!
diff --git a/test/800-smali/smali/b_27799205_1.smali b/test/800-smali/smali/b_27799205_1.smali
new file mode 100644
index 0000000..92bfc80
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_1.smali
@@ -0,0 +1,37 @@
+.class public LB27799205_1;
+.super Ljava/lang/Object;
+
+# A class with an unresolved array type should not fail hard (unless it's a primitive-type access).
+
+.method public static run()V
+.registers 1
+ return-void
+.end method
+
+.method public static test([Ljava/lang/Object;[Ldo/not/resolve/K;Z)V
+.registers 6
+ # Make v0, v1 and v2 null. We'll use v0 as a merge of the inputs, v1 as null, and v2 as 0.
+ const v0, 0
+ const v1, 0
+ const v2, 0
+
+ # Conditional jump so we have a merge point.
+ if-eqz v5, :LabelSelectUnresolved
+
+:LabelSelectResolved
+ move-object v0, v3
+ goto :LabelMerged
+
+:LabelSelectUnresolved
+ move-object v0, v4
+ goto :LabelMerged
+
+:LabelMerged
+ # At this point, v0 will be the unresolved merge.
+
+ # Test aput: v0[v2] = v1.
+ aput-object v1, v0, v2
+
+ return-void
+
+.end method
diff --git a/test/800-smali/smali/b_27799205_2.smali b/test/800-smali/smali/b_27799205_2.smali
new file mode 100644
index 0000000..e730b1e
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_2.smali
@@ -0,0 +1,37 @@
+.class public LB27799205_2;
+.super Ljava/lang/Object;
+
+# A class with an unresolved array type should not fail hard (unless it's a primitive-type access).
+
+.method public static run()V
+.registers 1
+ return-void
+.end method
+
+.method public static test([Ljava/lang/Object;[Ldo/not/resolve/K;Z)V
+.registers 6
+ # Make v0, v1 and v2 null. We'll use v0 as a merge of the inputs, v1 as null, and v2 as 0.
+ const v0, 0
+ const v1, 0
+ const v2, 0
+
+ # Conditional jump so we have a merge point.
+ if-eqz v5, :LabelSelectUnresolved
+
+:LabelSelectResolved
+ move-object v0, v3
+ goto :LabelMerged
+
+:LabelSelectUnresolved
+ move-object v0, v4
+ goto :LabelMerged
+
+:LabelMerged
+ # At this point, v0 will be the unresolved merge.
+
+ # Test aput: v0[v2] = v1.
+ aput v1, v0, v2
+
+ return-void
+
+.end method
diff --git a/test/800-smali/smali/b_27799205_3.smali b/test/800-smali/smali/b_27799205_3.smali
new file mode 100644
index 0000000..1cb025e
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_3.smali
@@ -0,0 +1,39 @@
+.class public LB27799205_3;
+.super Ljava/lang/Object;
+
+# A class with an unresolved array type should not fail hard (unless it's a primitive-type access).
+# Make sure that merging is pro-active.
+
+.method public static run()V
+.registers 1
+ return-void
+.end method
+
+# Use some non-object non-array input (non-Object because the merge should be Object).
+.method public static test(Ljava/lang/Integer;[Ldo/not/resolve/K;Z)V
+.registers 6
+ # Make v0, v1 and v2 null. We'll use v0 as a merge of the inputs, v1 as null, and v2 as 0.
+ const v0, 0
+ const v1, 0
+ const v2, 0
+
+ # Conditional jump so we have a merge point.
+ if-eqz v5, :LabelSelectUnresolved
+
+:LabelSelectResolved
+ move-object v0, v3
+ goto :LabelMerged
+
+:LabelSelectUnresolved
+ move-object v0, v4
+ goto :LabelMerged
+
+:LabelMerged
+ # At this point, v0 should be Object.
+
+ # Test aput-object: v0[v2] = v1. Should fail for v0 not being an array.
+ aput-object v1, v0, v2
+
+ return-void
+
+.end method
diff --git a/test/800-smali/smali/b_27799205_4.smali b/test/800-smali/smali/b_27799205_4.smali
new file mode 100644
index 0000000..e42951a
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_4.smali
@@ -0,0 +1,39 @@
+.class public LB27799205_4;
+.super Ljava/lang/Object;
+
+# A class with an unresolved array type should not fail hard (unless it's a primitive-type access).
+# Make sure that merging is pro-active.
+
+.method public static run()V
+.registers 1
+ return-void
+.end method
+
+# Use some primitive-type array input.
+.method public static test([I[Ldo/not/resolve/K;Z)V
+.registers 6
+ # Make v0, v1 and v2 null. We'll use v0 as a merge of the inputs, v1 as null, and v2 as 0.
+ const v0, 0
+ const v1, 0
+ const v2, 0
+
+ # Conditional jump so we have a merge point.
+ if-eqz v5, :LabelSelectUnresolved
+
+:LabelSelectResolved
+ move-object v0, v3
+ goto :LabelMerged
+
+:LabelSelectUnresolved
+ move-object v0, v4
+ goto :LabelMerged
+
+:LabelMerged
+ # At this point, v0 should be Object.
+
+ # Test aput-object: v0[v2] = v1. Should fail for v0 not being an array.
+ aput-object v1, v0, v2
+
+ return-void
+
+.end method
diff --git a/test/800-smali/smali/b_27799205_5.smali b/test/800-smali/smali/b_27799205_5.smali
new file mode 100644
index 0000000..6c7b183
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_5.smali
@@ -0,0 +1,39 @@
+.class public LB27799205_5;
+.super Ljava/lang/Object;
+
+# A class with an unresolved array type should not fail hard (unless it's a primitive-type access).
+# Make sure that merging is pro-active.
+
+.method public static run()V
+.registers 1
+ return-void
+.end method
+
+# Use some non-resolvable non-array type.
+.method public static test(Ldo/not/resolve/L;[Ldo/not/resolve/K;Z)V
+.registers 6
+ # Make v0, v1 and v2 null. We'll use v0 as a merge of the inputs, v1 as null, and v2 as 0.
+ const v0, 0
+ const v1, 0
+ const v2, 0
+
+ # Conditional jump so we have a merge point.
+ if-eqz v5, :LabelSelectUnresolved
+
+:LabelSelectResolved
+ move-object v0, v3
+ goto :LabelMerged
+
+:LabelSelectUnresolved
+ move-object v0, v4
+ goto :LabelMerged
+
+:LabelMerged
+ # At this point, v0 should be Object.
+
+ # Test aput-object: v0[v2] = v1. Should fail for v0 not being an array.
+ aput-object v1, v0, v2
+
+ return-void
+
+.end method
diff --git a/test/800-smali/smali/b_27799205_helper.smali b/test/800-smali/smali/b_27799205_helper.smali
new file mode 100644
index 0000000..145c93d
--- /dev/null
+++ b/test/800-smali/smali/b_27799205_helper.smali
@@ -0,0 +1,40 @@
+.class public LB27799205Helper;
+.super Ljava/lang/Object;
+
+# Helper for B27799205. Reflection tries to resolve all types. That's bad for intentionally
+# unresolved types. It makes it harder to distinguish what kind of error we got.
+
+.method public static run1()V
+.registers 1
+ invoke-static {}, LB27799205_1;->run()V
+
+ return-void
+.end method
+
+.method public static run2()V
+.registers 1
+ invoke-static {}, LB27799205_2;->run()V
+
+ return-void
+.end method
+
+.method public static run3()V
+.registers 1
+ invoke-static {}, LB27799205_3;->run()V
+
+ return-void
+.end method
+
+.method public static run4()V
+.registers 1
+ invoke-static {}, LB27799205_4;->run()V
+
+ return-void
+.end method
+
+.method public static run5()V
+.registers 1
+ invoke-static {}, LB27799205_5;->run()V
+
+ return-void
+.end method
diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java
index 4e6de46..20c3065 100644
--- a/test/800-smali/src/Main.java
+++ b/test/800-smali/src/Main.java
@@ -164,6 +164,15 @@
null));
testCases.add(new TestCase("b/26965384", "B26965384", "run", null, new VerifyError(),
null));
+ testCases.add(new TestCase("b/27799205 (1)", "B27799205Helper", "run1", null, null, null));
+ testCases.add(new TestCase("b/27799205 (2)", "B27799205Helper", "run2", null,
+ new VerifyError(), null));
+ testCases.add(new TestCase("b/27799205 (3)", "B27799205Helper", "run3", null,
+ new VerifyError(), null));
+ testCases.add(new TestCase("b/27799205 (4)", "B27799205Helper", "run4", null,
+ new VerifyError(), null));
+ testCases.add(new TestCase("b/27799205 (5)", "B27799205Helper", "run5", null,
+ new VerifyError(), null));
}
public void runTests() {