diff options
-rw-r--r-- | runtime/common_runtime_test.cc | 54 | ||||
-rw-r--r-- | runtime/common_runtime_test.h | 10 | ||||
-rw-r--r-- | runtime/monitor_test.cc | 65 | ||||
-rw-r--r-- | runtime/verifier/reg_type.cc | 35 | ||||
-rw-r--r-- | runtime/verifier/reg_type.h | 4 | ||||
-rw-r--r-- | runtime/verifier/reg_type_cache.cc | 5 | ||||
-rw-r--r-- | runtime/verifier/reg_type_cache.h | 4 | ||||
-rw-r--r-- | runtime/verifier/reg_type_test.cc | 52 |
8 files changed, 168 insertions, 61 deletions
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 8dda04efb8..7e762c33f2 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -785,6 +785,60 @@ std::string CommonRuntimeTestImpl::CreateClassPathWithChecksums( 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 daf9ac344e..74bc0b2afb 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -44,6 +44,8 @@ class DexFile; 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 @@ class CommonRuntimeTestImpl { // 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 27ce149342..fb12841345 100644 --- a/runtime/monitor_test.cc +++ b/runtime/monitor_test.cc @@ -36,11 +36,8 @@ class MonitorTest : public CommonRuntimeTest { 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 @@ class MonitorTest : public CommonRuntimeTest { 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 @@ static void CommonWaitSetup(MonitorTest* test, ClassLinker* class_linker, uint64 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 740b7dd7d4..883de38e66 100644 --- a/runtime/verifier/reg_type.cc +++ b/runtime/verifier/reg_type.cc @@ -711,6 +711,29 @@ const RegType& RegType::Merge(const RegType& incoming_type, 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 @@ mirror::Class* RegType::ClassJoin(mirror::Class* s, mirror::Class* t) { 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 6c01a7982a..c5d8ff5131 100644 --- a/runtime/verifier/reg_type.h +++ b/runtime/verifier/reg_type.h @@ -355,6 +355,10 @@ class RegType { * 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 93286ea84e..0c0086858d 100644 --- a/runtime/verifier/reg_type_cache.cc +++ b/runtime/verifier/reg_type_cache.cc @@ -222,6 +222,11 @@ const RegType& RegTypeCache::From(mirror::ClassLoader* loader, } } +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 37f8a1fc33..c9bf6a90fe 100644 --- a/runtime/verifier/reg_type_cache.h +++ b/runtime/verifier/reg_type_cache.h @@ -97,6 +97,10 @@ class RegTypeCache { 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 b0ea6c857c..fef13a2060 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 @@ TEST_F(RegTypeTest, ConstPrecision) { 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 |