Merge changes Ia8b678ab,I823201e3
* changes:
ART: Move FillHeap to CommonRuntimeTest
ART: Account for OOME during array merging
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 8dda04e..7e762c3 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -785,6 +785,60 @@
return classpath;
}
+void CommonRuntimeTestImpl::FillHeap(Thread* self,
+ ClassLinker* class_linker,
+ VariableSizedHandleScope* handle_scope) {
+ DCHECK(handle_scope != nullptr);
+
+ Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
+
+ // Class java.lang.Object.
+ Handle<mirror::Class> c(handle_scope->NewHandle(
+ class_linker->FindSystemClass(self, "Ljava/lang/Object;")));
+ // Array helps to fill memory faster.
+ Handle<mirror::Class> ca(handle_scope->NewHandle(
+ class_linker->FindSystemClass(self, "[Ljava/lang/Object;")));
+
+ // Start allocating with ~128K
+ size_t length = 128 * KB;
+ while (length > 40) {
+ const int32_t array_length = length / 4; // Object[] has elements of size 4.
+ MutableHandle<mirror::Object> h(handle_scope->NewHandle<mirror::Object>(
+ mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), array_length)));
+ if (self->IsExceptionPending() || h == nullptr) {
+ self->ClearException();
+
+ // Try a smaller length
+ length = length / 2;
+ // Use at most a quarter the reported free space.
+ size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
+ if (length * 4 > mem) {
+ length = mem / 4;
+ }
+ }
+ }
+
+ // Allocate simple objects till it fails.
+ while (!self->IsExceptionPending()) {
+ handle_scope->NewHandle<mirror::Object>(c->AllocObject(self));
+ }
+ self->ClearException();
+}
+
+void CommonRuntimeTestImpl::SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options) {
+ // Use a smaller heap
+ bool found = false;
+ for (std::pair<std::string, const void*>& pair : *options) {
+ if (pair.first.find("-Xmx") == 0) {
+ pair.first = "-Xmx4M"; // Smallest we can go.
+ found = true;
+ }
+ }
+ if (!found) {
+ options->emplace_back("-Xmx4M", nullptr);
+ }
+}
+
CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
vm_->SetCheckJniAbortHook(Hook, &actual_);
}
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index daf9ac3..74bc0b2 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -44,6 +44,8 @@
class JavaVMExt;
class Runtime;
typedef std::vector<std::pair<std::string, const void*>> RuntimeOptions;
+class Thread;
+class VariableSizedHandleScope;
uint8_t* DecodeBase64(const char* src, size_t* dst_size);
@@ -105,6 +107,14 @@
// Retuerns the filename for a test dex (i.e. XandY or ManyMethods).
std::string GetTestDexFileName(const char* name) const;
+ // A helper function to fill the heap.
+ static void FillHeap(Thread* self,
+ ClassLinker* class_linker,
+ VariableSizedHandleScope* handle_scope)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // A helper to set up a small heap (4M) to make FillHeap faster.
+ static void SetUpRuntimeOptionsForFillHeap(RuntimeOptions *options);
+
protected:
// Allow subclases such as CommonCompilerTest to add extra options.
virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) {}
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index 27ce149..fb12841 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -36,11 +36,8 @@
protected:
void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
// Use a smaller heap
- for (std::pair<std::string, const void*>& pair : *options) {
- if (pair.first.find("-Xmx") == 0) {
- pair.first = "-Xmx4M"; // Smallest we can go.
- }
- }
+ SetUpRuntimeOptionsForFillHeap(options);
+
options->push_back(std::make_pair("-Xint", nullptr));
}
public:
@@ -56,52 +53,6 @@
bool completed_;
};
-// Fill the heap.
-static const size_t kMaxHandles = 1000000; // Use arbitrary large amount for now.
-static void FillHeap(Thread* self, ClassLinker* class_linker,
- std::unique_ptr<StackHandleScope<kMaxHandles>>* hsp,
- std::vector<MutableHandle<mirror::Object>>* handles)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Runtime::Current()->GetHeap()->SetIdealFootprint(1 * GB);
-
- hsp->reset(new StackHandleScope<kMaxHandles>(self));
- // Class java.lang.Object.
- Handle<mirror::Class> c((*hsp)->NewHandle(class_linker->FindSystemClass(self,
- "Ljava/lang/Object;")));
- // Array helps to fill memory faster.
- Handle<mirror::Class> ca((*hsp)->NewHandle(class_linker->FindSystemClass(self,
- "[Ljava/lang/Object;")));
-
- // Start allocating with 128K
- size_t length = 128 * KB / 4;
- while (length > 10) {
- MutableHandle<mirror::Object> h((*hsp)->NewHandle<mirror::Object>(
- mirror::ObjectArray<mirror::Object>::Alloc(self, ca.Get(), length / 4)));
- if (self->IsExceptionPending() || h == nullptr) {
- self->ClearException();
-
- // Try a smaller length
- length = length / 8;
- // Use at most half the reported free space.
- size_t mem = Runtime::Current()->GetHeap()->GetFreeMemory();
- if (length * 8 > mem) {
- length = mem / 8;
- }
- } else {
- handles->push_back(h);
- }
- }
-
- // Allocate simple objects till it fails.
- while (!self->IsExceptionPending()) {
- MutableHandle<mirror::Object> h = (*hsp)->NewHandle<mirror::Object>(c->AllocObject(self));
- if (!self->IsExceptionPending() && h != nullptr) {
- handles->push_back(h);
- }
- }
- self->ClearException();
-}
-
// Check that an exception can be thrown correctly.
// This test is potentially racy, but the timeout is long enough that it should work.
@@ -304,16 +255,12 @@
test->complete_barrier_ = std::unique_ptr<Barrier>(new Barrier(3));
test->completed_ = false;
- // Fill the heap.
- std::unique_ptr<StackHandleScope<kMaxHandles>> hsp;
- std::vector<MutableHandle<mirror::Object>> handles;
-
// Our job: Fill the heap, then try Wait.
- FillHeap(soa.Self(), class_linker, &hsp, &handles);
+ {
+ VariableSizedHandleScope vhs(soa.Self());
+ test->FillHeap(soa.Self(), class_linker, &vhs);
- // Now release everything.
- for (MutableHandle<mirror::Object>& h : handles) {
- h.Assign(nullptr);
+ // Now release everything.
}
// Need to drop the mutator lock to allow barriers.
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 740b7dd..883de38 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -711,6 +711,29 @@
DCHECK(c1 != nullptr && !c1->IsPrimitive());
DCHECK(c2 != nullptr && !c2->IsPrimitive());
mirror::Class* join_class = ClassJoin(c1, c2);
+ if (UNLIKELY(join_class == nullptr)) {
+ // Internal error joining the classes (e.g., OOME). Report an unresolved reference type.
+ // We cannot report an unresolved merge type, as that will attempt to merge the resolved
+ // components, leaving us in an infinite loop.
+ // We do not want to report the originating exception, as that would require a fast path
+ // out all the way to VerifyClass. Instead attempt to continue on without a detailed type.
+ Thread* self = Thread::Current();
+ self->AssertPendingException();
+ self->ClearException();
+
+ // When compiling on the host, we rather want to abort to ensure determinism for preopting.
+ // (In that case, it is likely a misconfiguration of dex2oat.)
+ if (!kIsTargetBuild && Runtime::Current()->IsAotCompiler()) {
+ LOG(FATAL) << "Could not create class join of "
+ << c1->PrettyClass()
+ << " & "
+ << c2->PrettyClass();
+ UNREACHABLE();
+ }
+
+ return reg_types->MakeUnresolvedReference();
+ }
+
// Record the dependency that both `c1` and `c2` are assignable to `join_class`.
// The `verifier` is null during unit tests.
if (verifier != nullptr) {
@@ -753,10 +776,18 @@
DCHECK(result->IsObjectClass());
return result;
}
+ Thread* self = Thread::Current();
ObjPtr<mirror::Class> common_elem = ClassJoin(s_ct, t_ct);
+ if (UNLIKELY(common_elem == nullptr)) {
+ self->AssertPendingException();
+ return nullptr;
+ }
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- mirror::Class* array_class = class_linker->FindArrayClass(Thread::Current(), &common_elem);
- DCHECK(array_class != nullptr);
+ mirror::Class* array_class = class_linker->FindArrayClass(self, &common_elem);
+ if (UNLIKELY(array_class == nullptr)) {
+ self->AssertPendingException();
+ return nullptr;
+ }
return array_class;
} else {
size_t s_depth = s->Depth();
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index 6c01a79..c5d8ff5 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -355,6 +355,10 @@
* the perversion of Object being assignable to an interface type (note, however, that we don't
* allow assignment of Object or Interface to any concrete class and are therefore type safe).
*
+ * Note: This may return null in case of internal errors, e.g., OOME when a new class would have
+ * to be created but there is no heap space. The exception will stay pending, and it is
+ * the job of the caller to handle it.
+ *
* [1] Java bytecode verification: algorithms and formalizations, Xavier Leroy
*/
static mirror::Class* ClassJoin(mirror::Class* s, mirror::Class* t)
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 93286ea..0c00868 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -222,6 +222,11 @@
}
}
+const RegType& RegTypeCache::MakeUnresolvedReference() {
+ // The descriptor is intentionally invalid so nothing else will match this type.
+ return AddEntry(new (&arena_) UnresolvedReferenceType(AddString("a"), entries_.size()));
+}
+
const RegType* RegTypeCache::FindClass(mirror::Class* klass, bool precise) const {
DCHECK(klass != nullptr);
if (klass->IsPrimitive()) {
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index 37f8a1f..c9bf6a9 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -97,6 +97,10 @@
REQUIRES_SHARED(Locks::mutator_lock_);
const RegType& FromUnresolvedSuperClass(const RegType& child)
REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Note: this should not be used outside of RegType::ClassJoin!
+ const RegType& MakeUnresolvedReference() REQUIRES_SHARED(Locks::mutator_lock_);
+
const ConstantType& Zero() REQUIRES_SHARED(Locks::mutator_lock_) {
return FromCat1Const(0, true);
}
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index b0ea6c8..fef13a2 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -22,6 +22,7 @@
#include "base/casts.h"
#include "base/scoped_arena_allocator.h"
#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
#include "reg_type_cache-inl.h"
#include "reg_type-inl.h"
#include "scoped_thread_state_change-inl.h"
@@ -677,5 +678,56 @@
EXPECT_FALSE(imprecise_const.Equals(precise_const));
}
+class RegTypeOOMTest : public RegTypeTest {
+ protected:
+ void SetUpRuntimeOptions(RuntimeOptions *options) OVERRIDE {
+ SetUpRuntimeOptionsForFillHeap(options);
+
+ // We must not appear to be a compiler, or we'll abort on the host.
+ callbacks_.reset();
+ }
+};
+
+TEST_F(RegTypeOOMTest, ClassJoinOOM) {
+ // Tests that we don't abort with OOMs.
+
+ ArenaStack stack(Runtime::Current()->GetArenaPool());
+ ScopedArenaAllocator allocator(&stack);
+ ScopedObjectAccess soa(Thread::Current());
+
+ // We cannot allow moving GC. Otherwise we'd have to ensure the reg types are updated (reference
+ // reg types store a class pointer in a GCRoot, which is normally updated through active verifiers
+ // being registered with their thread), which is unnecessarily complex.
+ Runtime::Current()->GetHeap()->IncrementDisableMovingGC(soa.Self());
+
+ // We merge nested array of primitive wrappers. These have a join type of an array of Number of
+ // the same depth. We start with depth five, as we want at least two newly created classes to
+ // test recursion (it's just more likely that nobody uses such deep arrays in runtime bringup).
+ constexpr const char* kIntArrayFive = "[[[[[Ljava/lang/Integer;";
+ constexpr const char* kFloatArrayFive = "[[[[[Ljava/lang/Float;";
+ constexpr const char* kNumberArrayFour = "[[[[Ljava/lang/Number;";
+ constexpr const char* kNumberArrayFive = "[[[[[Ljava/lang/Number;";
+
+ RegTypeCache cache(true, allocator);
+ const RegType& int_array_array = cache.From(nullptr, kIntArrayFive, false);
+ ASSERT_TRUE(int_array_array.HasClass());
+ const RegType& float_array_array = cache.From(nullptr, kFloatArrayFive, false);
+ ASSERT_TRUE(float_array_array.HasClass());
+
+ // Check assumptions: the joined classes don't exist, yet.
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+ ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFour, nullptr) == nullptr);
+ ASSERT_TRUE(class_linker->LookupClass(soa.Self(), kNumberArrayFive, nullptr) == nullptr);
+
+ // Fill the heap.
+ VariableSizedHandleScope hs(soa.Self());
+ FillHeap(soa.Self(), class_linker, &hs);
+
+ const RegType& join_type = int_array_array.Merge(float_array_array, &cache, nullptr);
+ ASSERT_TRUE(join_type.IsUnresolvedReference());
+
+ Runtime::Current()->GetHeap()->DecrementDisableMovingGC(soa.Self());
+}
+
} // namespace verifier
} // namespace art