diff options
33 files changed, 693 insertions, 400 deletions
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 6fbb2bd441..58b6137c7a 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -1943,20 +1943,15 @@ void OatWriter::InitBssLayout(InstructionSet instruction_set) { DCHECK_EQ(bss_size_, 0u); if (HasBootImage()) { DCHECK(bss_string_entries_.empty()); - if (bss_method_entries_.empty() && bss_type_entries_.empty()) { - // Nothing to put to the .bss section. - return; - } + } + if (bss_method_entries_.empty() && + bss_type_entries_.empty() && + bss_string_entries_.empty()) { + // Nothing to put to the .bss section. + return; } - // Allocate space for app dex cache arrays in the .bss section. PointerSize pointer_size = GetInstructionSetPointerSize(instruction_set); - if (!HasBootImage()) { - for (const DexFile* dex_file : *dex_files_) { - DexCacheArraysLayout layout(pointer_size, dex_file); - bss_size_ += layout.Size(); - } - } bss_methods_offset_ = bss_size_; diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index f8f4eb2ae3..a249cacc93 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -331,8 +331,9 @@ static bool CheckInductionSetFullyRemoved(ArenaSet<HInstruction*>* iset) { HLoopOptimization::HLoopOptimization(HGraph* graph, CompilerDriver* compiler_driver, - HInductionVarAnalysis* induction_analysis) - : HOptimization(graph, kLoopOptimizationPassName), + HInductionVarAnalysis* induction_analysis, + OptimizingCompilerStats* stats) + : HOptimization(graph, kLoopOptimizationPassName, stats), compiler_driver_(compiler_driver), induction_range_(induction_analysis), loop_allocator_(nullptr), @@ -625,6 +626,7 @@ bool HLoopOptimization::OptimizeInnerLoop(LoopNode* node) { TryAssignLastValue(node->loop_info, main_phi, preheader, /*collect_loop_uses*/ true)) { Vectorize(node, body, exit, trip_count); graph_->SetHasSIMD(true); // flag SIMD usage + MaybeRecordStat(stats_, MethodCompilationStat::kLoopVectorized); return true; } return false; @@ -1724,6 +1726,7 @@ bool HLoopOptimization::VectorizeHalvingAddIdiom(LoopNode* node, vector_length_, is_unsigned, is_rounded)); + MaybeRecordStat(stats_, MethodCompilationStat::kLoopVectorizedIdiom); } else { GenerateVecOp(instruction, vector_map_->Get(r), vector_map_->Get(s), type); } diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index ba9126c5f6..f34751815b 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -34,7 +34,8 @@ class HLoopOptimization : public HOptimization { public: HLoopOptimization(HGraph* graph, CompilerDriver* compiler_driver, - HInductionVarAnalysis* induction_analysis); + HInductionVarAnalysis* induction_analysis, + OptimizingCompilerStats* stats); void Run() OVERRIDE; diff --git a/compiler/optimizing/loop_optimization_test.cc b/compiler/optimizing/loop_optimization_test.cc index b5b03d8f26..1c5603d00f 100644 --- a/compiler/optimizing/loop_optimization_test.cc +++ b/compiler/optimizing/loop_optimization_test.cc @@ -31,7 +31,7 @@ class LoopOptimizationTest : public CommonCompilerTest { allocator_(&pool_), graph_(CreateGraph(&allocator_)), iva_(new (&allocator_) HInductionVarAnalysis(graph_)), - loop_opt_(new (&allocator_) HLoopOptimization(graph_, nullptr, iva_)) { + loop_opt_(new (&allocator_) HLoopOptimization(graph_, nullptr, iva_, nullptr)) { BuildGraph(); } diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index e98c97cf9a..71d91ae38f 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -509,7 +509,7 @@ static HOptimization* BuildOptimization( } else if (opt_name == SideEffectsAnalysis::kSideEffectsAnalysisPassName) { return new (arena) SideEffectsAnalysis(graph); } else if (opt_name == HLoopOptimization::kLoopOptimizationPassName) { - return new (arena) HLoopOptimization(graph, driver, most_recent_induction); + return new (arena) HLoopOptimization(graph, driver, most_recent_induction, stats); } else if (opt_name == CHAGuardOptimization::kCHAGuardOptimizationPassName) { return new (arena) CHAGuardOptimization(graph); } else if (opt_name == CodeSinking::kCodeSinkingPassName) { @@ -770,7 +770,7 @@ void OptimizingCompiler::RunOptimizations(HGraph* graph, LICM* licm = new (arena) LICM(graph, *side_effects1, stats); HInductionVarAnalysis* induction = new (arena) HInductionVarAnalysis(graph); BoundsCheckElimination* bce = new (arena) BoundsCheckElimination(graph, *side_effects1, induction); - HLoopOptimization* loop = new (arena) HLoopOptimization(graph, driver, induction); + HLoopOptimization* loop = new (arena) HLoopOptimization(graph, driver, induction, stats); LoadStoreAnalysis* lsa = new (arena) LoadStoreAnalysis(graph); LoadStoreElimination* lse = new (arena) LoadStoreElimination(graph, *side_effects2, *lsa, stats); HSharpening* sharpening = new (arena) HSharpening( diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h index d6da73cc1c..ff49056798 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -63,6 +63,8 @@ enum MethodCompilationStat { kBooleanSimplified, kIntrinsicRecognized, kLoopInvariantMoved, + kLoopVectorized, + kLoopVectorizedIdiom, kSelectGenerated, kRemovedInstanceOf, kInlinedInvokeVirtualOrInterface, @@ -183,6 +185,8 @@ class OptimizingCompilerStats { case kBooleanSimplified : name = "BooleanSimplified"; break; case kIntrinsicRecognized : name = "IntrinsicRecognized"; break; case kLoopInvariantMoved : name = "LoopInvariantMoved"; break; + case kLoopVectorized : name = "LoopVectorized"; break; + case kLoopVectorizedIdiom : name = "LoopVectorizedIdiom"; break; case kSelectGenerated : name = "SelectGenerated"; break; case kRemovedInstanceOf: name = "RemovedInstanceOf"; break; case kInlinedInvokeVirtualOrInterface: name = "InlinedInvokeVirtualOrInterface"; break; diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index 6c0d492be6..277f611eb7 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -810,11 +810,11 @@ class JvmtiFunctions { } static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, - jobject object ATTRIBUTE_UNUSED, - jvmtiMonitorUsage* info_ptr ATTRIBUTE_UNUSED) { + jobject object, + jvmtiMonitorUsage* info_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_get_monitor_info); - return ERR(NOT_IMPLEMENTED); + return ObjectUtil::GetObjectMonitorUsage(env, object, info_ptr); } static jvmtiError GetFieldName(jvmtiEnv* env, diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index 93eee28c0a..d3f52f638d 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -226,7 +226,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 1, .can_get_current_contended_monitor = 0, - .can_get_monitor_info = 0, + .can_get_monitor_info = 1, .can_pop_frame = 0, .can_redefine_classes = 1, .can_signal_thread = 0, diff --git a/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc index 2506acac3a..89ce35256d 100644 --- a/openjdkjvmti/ti_object.cc +++ b/openjdkjvmti/ti_object.cc @@ -35,6 +35,8 @@ #include "mirror/object-inl.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" +#include "thread_list.h" +#include "ti_thread.h" namespace openjdkjvmti { @@ -73,4 +75,59 @@ jvmtiError ObjectUtil::GetObjectHashCode(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +jvmtiError ObjectUtil::GetObjectMonitorUsage( + jvmtiEnv* baseenv, jobject obj, jvmtiMonitorUsage* usage) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(baseenv); + if (obj == nullptr) { + return ERR(INVALID_OBJECT); + } + if (usage == nullptr) { + return ERR(NULL_POINTER); + } + art::Thread* self = art::Thread::Current(); + ThreadUtil::SuspendCheck(self); + art::JNIEnvExt* jni = self->GetJniEnv(); + std::vector<jthread> wait; + std::vector<jthread> notify_wait; + { + art::ScopedObjectAccess soa(self); // Now we know we have the shared lock. + art::ScopedThreadSuspension sts(self, art::kNative); + art::ScopedSuspendAll ssa("GetObjectMonitorUsage", /*long_suspend*/false); + art::ObjPtr<art::mirror::Object> target(self->DecodeJObject(obj)); + // This gets the list of threads trying to lock or wait on the monitor. + art::MonitorInfo info(target.Ptr()); + usage->owner = info.owner_ != nullptr ? + jni->AddLocalReference<jthread>(info.owner_->GetPeerFromOtherThread()) : nullptr; + usage->entry_count = info.entry_count_; + for (art::Thread* thd : info.waiters_) { + // RI seems to consider waiting for notify to be included in those waiting to acquire the + // monitor. We will match this behavior. + notify_wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread())); + wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread())); + } + { + // Scan all threads to see which are waiting on this particular monitor. + art::MutexLock tll(self, *art::Locks::thread_list_lock_); + for (art::Thread* thd : art::Runtime::Current()->GetThreadList()->GetList()) { + if (thd != info.owner_ && target.Ptr() == thd->GetMonitorEnterObject()) { + wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread())); + } + } + } + } + usage->waiter_count = wait.size(); + usage->notify_waiter_count = notify_wait.size(); + jvmtiError ret = CopyDataIntoJvmtiBuffer(env, + reinterpret_cast<const unsigned char*>(wait.data()), + wait.size() * sizeof(jthread), + reinterpret_cast<unsigned char**>(&usage->waiters)); + if (ret != OK) { + return ret; + } + return CopyDataIntoJvmtiBuffer(env, + reinterpret_cast<const unsigned char*>(notify_wait.data()), + notify_wait.size() * sizeof(jthread), + reinterpret_cast<unsigned char**>(&usage->notify_waiters)); +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h index fa3bd0f51a..977ec3950f 100644 --- a/openjdkjvmti/ti_object.h +++ b/openjdkjvmti/ti_object.h @@ -42,6 +42,8 @@ class ObjectUtil { static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr); static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr); + + static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, jobject object, jvmtiMonitorUsage* usage); }; } // namespace openjdkjvmti diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index d22482f9e3..efef97517b 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1189,219 +1189,41 @@ class FixupInternVisitor { } }; -// Copies data from one array to another array at the same position -// if pred returns false. If there is a page of continuous data in -// the src array for which pred consistently returns true then -// corresponding page in the dst array will not be touched. -// This should reduce number of allocated physical pages. -template <class T, class NullPred> -static void CopyNonNull(const T* src, size_t count, T* dst, const NullPred& pred) { - for (size_t i = 0; i < count; ++i) { - if (!pred(src[i])) { - dst[i] = src[i]; - } - } -} - -template <typename T> -static void CopyDexCachePairs(const std::atomic<mirror::DexCachePair<T>>* src, - size_t count, - std::atomic<mirror::DexCachePair<T>>* dst) { - DCHECK_NE(count, 0u); - DCHECK(!src[0].load(std::memory_order_relaxed).object.IsNull() || - src[0].load(std::memory_order_relaxed).index != 0u); - for (size_t i = 0; i < count; ++i) { - DCHECK_EQ(dst[i].load(std::memory_order_relaxed).index, 0u); - DCHECK(dst[i].load(std::memory_order_relaxed).object.IsNull()); - mirror::DexCachePair<T> source = src[i].load(std::memory_order_relaxed); - if (source.index != 0u || !source.object.IsNull()) { - dst[i].store(source, std::memory_order_relaxed); - } - } -} - -template <typename T> -static void CopyNativeDexCachePairs(std::atomic<mirror::NativeDexCachePair<T>>* src, - size_t count, - std::atomic<mirror::NativeDexCachePair<T>>* dst, - PointerSize pointer_size) { - DCHECK_NE(count, 0u); - DCHECK(mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).object != nullptr || - mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).index != 0u); - for (size_t i = 0; i < count; ++i) { - DCHECK_EQ(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).index, 0u); - DCHECK(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).object == nullptr); - mirror::NativeDexCachePair<T> source = - mirror::DexCache::GetNativePairPtrSize(src, i, pointer_size); - if (source.index != 0u || source.object != nullptr) { - mirror::DexCache::SetNativePairPtrSize(dst, i, source, pointer_size); - } - } -} - // new_class_set is the set of classes that were read from the class table section in the image. // If there was no class table section, it is null. // Note: using a class here to avoid having to make ClassLinker internals public. class AppImageClassLoadersAndDexCachesHelper { public: - static bool Update( + static void Update( ClassLinker* class_linker, gc::space::ImageSpace* space, Handle<mirror::ClassLoader> class_loader, Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches, - ClassTable::ClassSet* new_class_set, - bool* out_forward_dex_cache_array, - std::string* out_error_msg) + ClassTable::ClassSet* new_class_set) REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_); }; -bool AppImageClassLoadersAndDexCachesHelper::Update( +void AppImageClassLoadersAndDexCachesHelper::Update( ClassLinker* class_linker, gc::space::ImageSpace* space, Handle<mirror::ClassLoader> class_loader, Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches, - ClassTable::ClassSet* new_class_set, - bool* out_forward_dex_cache_array, - std::string* out_error_msg) + ClassTable::ClassSet* new_class_set) REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(out_forward_dex_cache_array != nullptr); - DCHECK(out_error_msg != nullptr); - PointerSize image_pointer_size = class_linker->GetImagePointerSize(); Thread* const self = Thread::Current(); gc::Heap* const heap = Runtime::Current()->GetHeap(); const ImageHeader& header = space->GetImageHeader(); { - // Add image classes into the class table for the class loader, and fixup the dex caches and - // class loader fields. + // Register dex caches with the class loader. WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); - // Dex cache array fixup is all or nothing, we must reject app images that have mixed since we - // rely on clobering the dex cache arrays in the image to forward to bss. - size_t num_dex_caches_with_bss_arrays = 0; const size_t num_dex_caches = dex_caches->GetLength(); for (size_t i = 0; i < num_dex_caches; i++) { - ObjPtr<mirror::DexCache> const dex_cache = dex_caches->Get(i); - const DexFile* const dex_file = dex_cache->GetDexFile(); - const OatFile::OatDexFile* oat_dex_file = dex_file->GetOatDexFile(); - if (oat_dex_file != nullptr && oat_dex_file->GetDexCacheArrays() != nullptr) { - ++num_dex_caches_with_bss_arrays; - } - } - *out_forward_dex_cache_array = num_dex_caches_with_bss_arrays != 0; - if (*out_forward_dex_cache_array) { - if (num_dex_caches_with_bss_arrays != num_dex_caches) { - // Reject application image since we cannot forward only some of the dex cache arrays. - // TODO: We could get around this by having a dedicated forwarding slot. It should be an - // uncommon case. - *out_error_msg = StringPrintf("Dex caches in bss does not match total: %zu vs %zu", - num_dex_caches_with_bss_arrays, - num_dex_caches); - return false; - } - } - - // Only add the classes to the class loader after the points where we can return false. - for (size_t i = 0; i < num_dex_caches; i++) { ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i); const DexFile* const dex_file = dex_cache->GetDexFile(); - const OatFile::OatDexFile* oat_dex_file = dex_file->GetOatDexFile(); - if (oat_dex_file != nullptr && oat_dex_file->GetDexCacheArrays() != nullptr) { - // If the oat file expects the dex cache arrays to be in the BSS, then allocate there and - // copy over the arrays. - DCHECK(dex_file != nullptr); - size_t num_strings = mirror::DexCache::kDexCacheStringCacheSize; - if (dex_file->NumStringIds() < num_strings) { - num_strings = dex_file->NumStringIds(); - } - size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize; - if (dex_file->NumTypeIds() < num_types) { - num_types = dex_file->NumTypeIds(); - } - size_t num_methods = mirror::DexCache::kDexCacheMethodCacheSize; - if (dex_file->NumMethodIds() < num_methods) { - num_methods = dex_file->NumMethodIds(); - } - size_t num_fields = mirror::DexCache::kDexCacheFieldCacheSize; - if (dex_file->NumFieldIds() < num_fields) { - num_fields = dex_file->NumFieldIds(); - } - size_t num_method_types = mirror::DexCache::kDexCacheMethodTypeCacheSize; - if (dex_file->NumProtoIds() < num_method_types) { - num_method_types = dex_file->NumProtoIds(); - } - const size_t num_call_sites = dex_file->NumCallSiteIds(); - CHECK_EQ(num_strings, dex_cache->NumStrings()); - CHECK_EQ(num_types, dex_cache->NumResolvedTypes()); - CHECK_EQ(num_methods, dex_cache->NumResolvedMethods()); - CHECK_EQ(num_fields, dex_cache->NumResolvedFields()); - CHECK_EQ(num_method_types, dex_cache->NumResolvedMethodTypes()); - CHECK_EQ(num_call_sites, dex_cache->NumResolvedCallSites()); - DexCacheArraysLayout layout(image_pointer_size, dex_file); - uint8_t* const raw_arrays = oat_dex_file->GetDexCacheArrays(); - if (num_strings != 0u) { - mirror::StringDexCacheType* const image_resolved_strings = dex_cache->GetStrings(); - mirror::StringDexCacheType* const strings = - reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset()); - CopyDexCachePairs(image_resolved_strings, num_strings, strings); - dex_cache->SetStrings(strings); - } - if (num_types != 0u) { - mirror::TypeDexCacheType* const image_resolved_types = dex_cache->GetResolvedTypes(); - mirror::TypeDexCacheType* const types = - reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset()); - CopyDexCachePairs(image_resolved_types, num_types, types); - dex_cache->SetResolvedTypes(types); - } - if (num_methods != 0u) { - mirror::MethodDexCacheType* const image_resolved_methods = - dex_cache->GetResolvedMethods(); - mirror::MethodDexCacheType* const methods = - reinterpret_cast<mirror::MethodDexCacheType*>(raw_arrays + layout.MethodsOffset()); - CopyNativeDexCachePairs(image_resolved_methods, num_methods, methods, image_pointer_size); - dex_cache->SetResolvedMethods(methods); - } - if (num_fields != 0u) { - mirror::FieldDexCacheType* const image_resolved_fields = dex_cache->GetResolvedFields(); - mirror::FieldDexCacheType* const fields = - reinterpret_cast<mirror::FieldDexCacheType*>(raw_arrays + layout.FieldsOffset()); - CopyNativeDexCachePairs(image_resolved_fields, num_fields, fields, image_pointer_size); - dex_cache->SetResolvedFields(fields); - } - if (num_method_types != 0u) { - // NOTE: We currently (Sep 2016) do not resolve any method types at - // compile time, but plan to in the future. This code exists for the - // sake of completeness. - mirror::MethodTypeDexCacheType* const image_resolved_method_types = - dex_cache->GetResolvedMethodTypes(); - mirror::MethodTypeDexCacheType* const method_types = - reinterpret_cast<mirror::MethodTypeDexCacheType*>( - raw_arrays + layout.MethodTypesOffset()); - CopyDexCachePairs(image_resolved_method_types, num_method_types, method_types); - dex_cache->SetResolvedMethodTypes(method_types); - } - if (num_call_sites != 0u) { - GcRoot<mirror::CallSite>* const image_resolved_call_sites = - dex_cache->GetResolvedCallSites(); - GcRoot<mirror::CallSite>* const call_sites = - reinterpret_cast<GcRoot<mirror::CallSite>*>(raw_arrays + layout.CallSitesOffset()); - for (size_t j = 0; kIsDebugBuild && j < num_call_sites; ++j) { - DCHECK(call_sites[j].IsNull()); - } - CopyNonNull(image_resolved_call_sites, - num_call_sites, - call_sites, - [](const GcRoot<mirror::CallSite>& elem) { - return elem.IsNull(); - }); - dex_cache->SetResolvedCallSites(call_sites); - } - } { WriterMutexLock mu2(self, *Locks::dex_lock_); - // Make sure to do this after we update the arrays since we store the resolved types array - // in DexCacheData in RegisterDexFileLocked. We need the array pointer to be the one in the - // BSS. CHECK(!class_linker->FindDexCacheDataLocked(*dex_file).IsValid()); class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get()); } @@ -1468,7 +1290,6 @@ bool AppImageClassLoadersAndDexCachesHelper::Update( VerifyDeclaringClassVisitor visitor; header.VisitPackedArtMethods(&visitor, space->Begin(), kRuntimePointerSize); } - return true; } // Update the class loader. Should only be used on classes in the image space. @@ -1947,7 +1768,7 @@ bool ClassLinker::AddImageSpace( // If we have a class table section, read it and use it for verification in // UpdateAppImageClassLoadersAndDexCaches. ClassTable::ClassSet temp_set; - const ImageSection& class_table_section = header.GetImageSection(ImageHeader::kSectionClassTable); + const ImageSection& class_table_section = header.GetClassTableSection(); const bool added_class_table = class_table_section.Size() > 0u; if (added_class_table) { const uint64_t start_time2 = NanoTime(); @@ -1958,37 +1779,17 @@ bool ClassLinker::AddImageSpace( VLOG(image) << "Adding class table classes took " << PrettyDuration(NanoTime() - start_time2); } if (app_image) { - bool forward_dex_cache_arrays = false; - if (!AppImageClassLoadersAndDexCachesHelper::Update(this, - space, - class_loader, - dex_caches, - &temp_set, - /*out*/&forward_dex_cache_arrays, - /*out*/error_msg)) { - return false; - } + AppImageClassLoadersAndDexCachesHelper::Update(this, + space, + class_loader, + dex_caches, + &temp_set); // Update class loader and resolved strings. If added_class_table is false, the resolved // strings were forwarded UpdateAppImageClassLoadersAndDexCaches. UpdateClassLoaderVisitor visitor(space, class_loader.Get()); for (const ClassTable::TableSlot& root : temp_set) { visitor(root.Read()); } - // forward_dex_cache_arrays is true iff we copied all of the dex cache arrays into the .bss. - // In this case, madvise away the dex cache arrays section of the image to reduce RAM usage and - // mark as PROT_NONE to catch any invalid accesses. - if (forward_dex_cache_arrays) { - const ImageSection& dex_cache_section = header.GetDexCacheArraysSection(); - uint8_t* section_begin = AlignUp(space->Begin() + dex_cache_section.Offset(), kPageSize); - uint8_t* section_end = AlignDown(space->Begin() + dex_cache_section.End(), kPageSize); - if (section_begin < section_end) { - madvise(section_begin, section_end - section_begin, MADV_DONTNEED); - mprotect(section_begin, section_end - section_begin, PROT_NONE); - VLOG(image) << "Released and protected dex cache array image section from " - << reinterpret_cast<const void*>(section_begin) << "-" - << reinterpret_cast<const void*>(section_end); - } - } } if (!oat_file->GetBssGcRoots().empty()) { // Insert oat file to class table for visiting .bss GC roots. @@ -7951,7 +7752,8 @@ ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, // We have a valid method from the DexCache but we need to perform ICCE and IAE checks. DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex(); klass = LookupResolvedType(dex_file, method_id.class_idx_, dex_cache.Get(), class_loader.Get()); - DCHECK(klass != nullptr); + CHECK(klass != nullptr) << resolved->PrettyMethod() << " " << resolved << " " + << resolved->GetAccessFlags(); } else { // The method was not in the DexCache, resolve the declaring class. klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader); diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc index 07afedf649..56573f550e 100644 --- a/runtime/class_loader_context.cc +++ b/runtime/class_loader_context.cc @@ -632,6 +632,10 @@ std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoa } } +static bool IsAbsoluteLocation(const std::string& location) { + return !location.empty() && location[0] == '/'; +} + bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const { ClassLoaderContext expected_context; if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) { @@ -673,18 +677,52 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size()); for (size_t k = 0; k < info.classpath.size(); k++) { - if (info.classpath[k] != expected_info.classpath[k]) { + // Compute the dex location that must be compared. + // We shouldn't do a naive comparison `info.classpath[k] == expected_info.classpath[k]` + // because even if they refer to the same file, one could be encoded as a relative location + // and the other as an absolute one. + bool is_dex_name_absolute = IsAbsoluteLocation(info.classpath[k]); + bool is_expected_dex_name_absolute = IsAbsoluteLocation(expected_info.classpath[k]); + std::string dex_name; + std::string expected_dex_name; + + if (is_dex_name_absolute == is_expected_dex_name_absolute) { + // If both locations are absolute or relative then compare them as they are. + // This is usually the case for: shared libraries and secondary dex files. + dex_name = info.classpath[k]; + expected_dex_name = expected_info.classpath[k]; + } else if (is_dex_name_absolute) { + // The runtime name is absolute but the compiled name (the expected one) is relative. + // This is the case for split apks which depend on base or on other splits. + dex_name = info.classpath[k]; + expected_dex_name = OatFile::ResolveRelativeEncodedDexLocation( + info.classpath[k].c_str(), expected_info.classpath[k]); + } else { + // The runtime name is relative but the compiled name is absolute. + // There is no expected use case that would end up here as dex files are always loaded + // with their absolute location. However, be tolerant and do the best effort (in case + // there are unexpected new use case...). + DCHECK(is_expected_dex_name_absolute); + dex_name = OatFile::ResolveRelativeEncodedDexLocation( + expected_info.classpath[k].c_str(), info.classpath[k]); + expected_dex_name = expected_info.classpath[k]; + } + + // Compare the locations. + if (dex_name != expected_dex_name) { LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i << ". expected=" << expected_info.classpath[k] << ", found=" << info.classpath[k] << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } + + // Compare the checksums. if (info.checksums[k] != expected_info.checksums[k]) { LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i - << ". expected=" << expected_info.checksums[k] - << ", found=" << info.checksums[k] - << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; + << ". expected=" << expected_info.checksums[k] + << ", found=" << info.checksums[k] + << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")"; return false; } } diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc index ddbb73b5b2..18472743fb 100644 --- a/runtime/class_loader_context_test.cc +++ b/runtime/class_loader_context_test.cc @@ -697,7 +697,17 @@ TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) { std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d); - ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context->EncodeContextForOatFile(""))); + std::string context_with_no_base_dir = context->EncodeContextForOatFile(""); + ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context_with_no_base_dir)); + + std::string dex_location = GetTestDexFileName("ForClassLoaderA"); + size_t pos = dex_location.rfind('/'); + ASSERT_NE(std::string::npos, pos); + std::string parent = dex_location.substr(0, pos); + + std::string context_with_base_dir = context->EncodeContextForOatFile(parent); + ASSERT_NE(context_with_base_dir, context_with_no_base_dir); + ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context_with_base_dir)); } TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncodingMultidex) { diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc index f6f20ba3ab..2f63dff3be 100644 --- a/runtime/mirror/dex_cache.cc +++ b/runtime/mirror/dex_cache.cc @@ -46,13 +46,10 @@ void DexCache::InitializeDexCache(Thread* self, DexCacheArraysLayout layout(image_pointer_size, dex_file); uint8_t* raw_arrays = nullptr; - const OatDexFile* const oat_dex = dex_file->GetOatDexFile(); - if (oat_dex != nullptr && oat_dex->GetDexCacheArrays() != nullptr) { - raw_arrays = oat_dex->GetDexCacheArrays(); - } else if (dex_file->NumStringIds() != 0u || - dex_file->NumTypeIds() != 0u || - dex_file->NumMethodIds() != 0u || - dex_file->NumFieldIds() != 0u) { + if (dex_file->NumStringIds() != 0u || + dex_file->NumTypeIds() != 0u || + dex_file->NumMethodIds() != 0u || + dex_file->NumFieldIds() != 0u) { static_assert(ArenaAllocator::kAlignment == 8, "Expecting arena alignment of 8."); DCHECK(layout.Alignment() == 8u || layout.Alignment() == 16u); // Zero-initialized. diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 5c63dcad78..80e6ad3d47 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -1498,13 +1498,21 @@ MonitorInfo::MonitorInfo(mirror::Object* obj) : owner_(nullptr), entry_count_(0) break; case LockWord::kThinLocked: owner_ = Runtime::Current()->GetThreadList()->FindThreadByThreadId(lock_word.ThinLockOwner()); + DCHECK(owner_ != nullptr) << "Thin-locked without owner!"; entry_count_ = 1 + lock_word.ThinLockCount(); // Thin locks have no waiters. break; case LockWord::kFatLocked: { Monitor* mon = lock_word.FatLockMonitor(); owner_ = mon->owner_; - entry_count_ = 1 + mon->lock_count_; + // Here it is okay for the owner to be null since we don't reset the LockWord back to + // kUnlocked until we get a GC. In cases where this hasn't happened yet we will have a fat + // lock without an owner. + if (owner_ != nullptr) { + entry_count_ = 1 + mon->lock_count_; + } else { + DCHECK_EQ(mon->lock_count_, 0) << "Monitor is fat-locked without any owner!"; + } for (Thread* waiter = mon->wait_set_; waiter != nullptr; waiter = waiter->GetWaitNext()) { waiters_.push_back(waiter); } diff --git a/runtime/oat.h b/runtime/oat.h index 1d79ed6dea..ab7c42efbe 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,8 +32,8 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' }; - // Last oat version changed reason: Add dex section layout info to header. - static constexpr uint8_t kOatVersion[] = { '1', '3', '2', '\0' }; + // Last oat version changed reason: Remove DexCache arrays from .bss. + static constexpr uint8_t kOatVersion[] = { '1', '3', '3', '\0' }; static constexpr const char* kImageLocationKey = "image-location"; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index d9cfa533ca..200681ecb1 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -55,7 +55,6 @@ #include "type_lookup_table.h" #include "utf-inl.h" #include "utils.h" -#include "utils/dex_cache_arrays_layout-inl.h" #include "vdex_file.h" namespace art { @@ -279,36 +278,6 @@ inline static bool ReadOatDexFileData(const OatFile& oat_file, return true; } -static bool FindDexFileMapItem(const uint8_t* dex_begin, - const uint8_t* dex_end, - DexFile::MapItemType map_item_type, - const DexFile::MapItem** result_item) { - *result_item = nullptr; - - const DexFile::Header* header = - BoundsCheckedCast<const DexFile::Header*>(dex_begin, dex_begin, dex_end); - if (nullptr == header) return false; - - if (!DexFile::IsMagicValid(header->magic_)) return true; // Not a dex file, not an error. - - const DexFile::MapList* map_list = - BoundsCheckedCast<const DexFile::MapList*>(dex_begin + header->map_off_, dex_begin, dex_end); - if (nullptr == map_list) return false; - - const DexFile::MapItem* map_item = map_list->list_; - size_t count = map_list->size_; - while (count--) { - if (map_item->type_ == static_cast<uint16_t>(map_item_type)) { - *result_item = map_item; - break; - } - map_item = BoundsCheckedCast<const DexFile::MapItem*>(map_item + 1, dex_begin, dex_end); - if (nullptr == map_item) return false; - } - - return true; -} - bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { if (!GetOatHeader().IsValid()) { std::string cause = GetOatHeader().GetValidationErrorMessage(); @@ -361,7 +330,7 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { (bss_roots_ != nullptr && (bss_roots_ < bss_begin_ || bss_roots_ > bss_end_)) || (bss_methods_ != nullptr && bss_roots_ != nullptr && bss_methods_ > bss_roots_)) { *error_msg = StringPrintf("In oat file '%s' found bss symbol(s) outside .bss or unordered: " - "begin = %p, methods_ = %p, roots = %p, end = %p", + "begin = %p, methods = %p, roots = %p, end = %p", GetLocation().c_str(), bss_begin_, bss_methods_, @@ -370,11 +339,15 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { return false; } - uint8_t* after_arrays = (bss_methods_ != nullptr) ? bss_methods_ : bss_roots_; // May be null. - uint8_t* dex_cache_arrays = (bss_begin_ == after_arrays) ? nullptr : bss_begin_; - uint8_t* dex_cache_arrays_end = - (bss_begin_ == after_arrays) ? nullptr : (after_arrays != nullptr) ? after_arrays : bss_end_; - DCHECK_EQ(dex_cache_arrays != nullptr, dex_cache_arrays_end != nullptr); + if (bss_methods_ != nullptr && bss_methods_ != bss_begin_) { + *error_msg = StringPrintf("In oat file '%s' found unexpected .bss gap before 'oatbssmethods': " + "begin = %p, methods = %p", + GetLocation().c_str(), + bss_begin_, + bss_methods_); + return false; + } + uint32_t dex_file_count = GetOatHeader().GetDexFileCount(); oat_dex_files_storage_.reserve(dex_file_count); for (size_t i = 0; i < dex_file_count; i++) { @@ -609,37 +582,6 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { reinterpret_cast<const DexFile::Header*>(dex_file_pointer)->method_ids_size_); } - uint8_t* current_dex_cache_arrays = nullptr; - if (dex_cache_arrays != nullptr) { - // All DexCache types except for CallSite have their instance counts in the - // DexFile header. For CallSites, we need to read the info from the MapList. - const DexFile::MapItem* call_sites_item = nullptr; - if (!FindDexFileMapItem(DexBegin(), - DexEnd(), - DexFile::MapItemType::kDexTypeCallSiteIdItem, - &call_sites_item)) { - *error_msg = StringPrintf("In oat file '%s' could not read data from truncated DexFile map", - GetLocation().c_str()); - return false; - } - size_t num_call_sites = call_sites_item == nullptr ? 0 : call_sites_item->size_; - DexCacheArraysLayout layout(pointer_size, *header, num_call_sites); - if (layout.Size() != 0u) { - if (static_cast<size_t>(dex_cache_arrays_end - dex_cache_arrays) < layout.Size()) { - *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with " - "truncated dex cache arrays, %zu < %zu.", - GetLocation().c_str(), - i, - dex_file_location.c_str(), - static_cast<size_t>(dex_cache_arrays_end - dex_cache_arrays), - layout.Size()); - return false; - } - current_dex_cache_arrays = dex_cache_arrays; - dex_cache_arrays += layout.Size(); - } - } - std::string canonical_location = DexFile::GetDexCanonicalLocation(dex_file_location.c_str()); // Create the OatDexFile and add it to the owning container. @@ -651,7 +593,6 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { lookup_table_data, method_bss_mapping, class_offsets_pointer, - current_dex_cache_arrays, dex_layout_sections); oat_dex_files_storage_.push_back(oat_dex_file); @@ -664,14 +605,6 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { } } - if (dex_cache_arrays != dex_cache_arrays_end) { - // We expect the bss section to be either empty (dex_cache_arrays and bss_end_ - // both null) or contain just the dex cache arrays and optionally some GC roots. - *error_msg = StringPrintf("In oat file '%s' found unexpected bss size bigger by %zu bytes.", - GetLocation().c_str(), - static_cast<size_t>(bss_end_ - dex_cache_arrays)); - return false; - } return true; } @@ -1379,7 +1312,6 @@ OatFile::OatDexFile::OatDexFile(const OatFile* oat_file, const uint8_t* lookup_table_data, const MethodBssMapping* method_bss_mapping_data, const uint32_t* oat_class_offsets_pointer, - uint8_t* dex_cache_arrays, const DexLayoutSections* dex_layout_sections) : oat_file_(oat_file), dex_file_location_(dex_file_location), @@ -1389,7 +1321,6 @@ OatFile::OatDexFile::OatDexFile(const OatFile* oat_file, lookup_table_data_(lookup_table_data), method_bss_mapping_(method_bss_mapping_data), oat_class_offsets_pointer_(oat_class_offsets_pointer), - dex_cache_arrays_(dex_cache_arrays), dex_layout_sections_(dex_layout_sections) { // Initialize TypeLookupTable. if (lookup_table_data_ != nullptr) { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 9a7fe51e8e..e13126bae3 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -422,10 +422,6 @@ class OatDexFile FINAL { // Returns the offset to the OatClass information. Most callers should use GetOatClass. uint32_t GetOatClassOffset(uint16_t class_def_index) const; - uint8_t* GetDexCacheArrays() const { - return dex_cache_arrays_; - } - const uint8_t* GetLookupTableData() const { return lookup_table_data_; } @@ -470,7 +466,6 @@ class OatDexFile FINAL { const uint8_t* lookup_table_data, const MethodBssMapping* method_bss_mapping, const uint32_t* oat_class_offsets_pointer, - uint8_t* dex_cache_arrays, const DexLayoutSections* dex_layout_sections); static void AssertAotCompiler(); @@ -483,7 +478,6 @@ class OatDexFile FINAL { const uint8_t* const lookup_table_data_ = nullptr; const MethodBssMapping* const method_bss_mapping_ = nullptr; const uint32_t* const oat_class_offsets_pointer_ = 0u; - uint8_t* const dex_cache_arrays_ = nullptr; mutable std::unique_ptr<TypeLookupTable> lookup_table_; const DexLayoutSections* const dex_layout_sections_ = nullptr; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index a8ccf89cb2..a67a6aaeaa 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -417,6 +417,12 @@ struct AbortState { return; } Thread* self = Thread::Current(); + + // Dump all threads first and then the aborting thread. While this is counter the logical flow, + // it improves the chance of relevant data surviving in the Android logs. + + DumpAllThreads(os, self); + if (self == nullptr) { os << "(Aborting thread was not attached to runtime!)\n"; DumpKernelStack(os, GetTid(), " kernel: ", false); @@ -432,7 +438,6 @@ struct AbortState { } } } - DumpAllThreads(os, self); } // No thread-safety analysis as we do explicitly test for holding the mutator lock. diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java index 13a96c7edc..f5cbc2ae7a 100644 --- a/test/088-monitor-verification/src/Main.java +++ b/test/088-monitor-verification/src/Main.java @@ -39,6 +39,7 @@ public class Main { ensureJitCompiled(Main.class, "constantLock"); ensureJitCompiled(Main.class, "notExcessiveNesting"); ensureJitCompiled(Main.class, "notNested"); + ensureJitCompiled(TwoPath.class, "twoPath"); Main m = new Main(); diff --git a/test/1930-monitor-info/expected.txt b/test/1930-monitor-info/expected.txt new file mode 100644 index 0000000000..b43f1b20b6 --- /dev/null +++ b/test/1930-monitor-info/expected.txt @@ -0,0 +1,31 @@ +Running with single thread. +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Running with single thread in native. +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Lock twice +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: main, entryCount: 2, waiters: [], notify_waiters: [] } +Lock twice native +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: main, entryCount: 2, waiters: [], notify_waiters: [] } +Lock twice Java then native +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: main, entryCount: 2, waiters: [], notify_waiters: [] } +Lock twice native then Java +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: main, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: main, entryCount: 2, waiters: [], notify_waiters: [] } +lock with wait +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: main, entryCount: 1, waiters: [Test1930 Thread - testLockWait], notify_waiters: [] } +Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } +Wait for notify. +Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: main, entryCount: 1, waiters: [Test1930 Thread - testLockWait], notify_waiters: [Test1930 Thread - testLockWait] } +Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] } +Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] } diff --git a/test/1930-monitor-info/info.txt b/test/1930-monitor-info/info.txt new file mode 100644 index 0000000000..8e19edce92 --- /dev/null +++ b/test/1930-monitor-info/info.txt @@ -0,0 +1,3 @@ +Tests basic functions in the jvmti plugin. + +Tests that the GetObjectMonitorUsage function works correctly. diff --git a/test/1930-monitor-info/monitor.cc b/test/1930-monitor-info/monitor.cc new file mode 100644 index 0000000000..7f97c05a73 --- /dev/null +++ b/test/1930-monitor-info/monitor.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <pthread.h> + +#include <cstdio> +#include <iostream> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "jvmti.h" + +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1930MonitorInfo { + +extern "C" JNIEXPORT void JNICALL Java_art_Test1930_executeLockedNative(JNIEnv* env, + jclass klass, + jobject run, + jobject l) { + ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable")); + if (env->ExceptionCheck()) { + return; + } + jmethodID method = env->GetMethodID(runnable.get(), "run", "()V"); + + if (env->ExceptionCheck()) { + return; + } + jmethodID printMethod = env->GetStaticMethodID(klass, "printPreLock", "(Ljava/lang/Object;)V"); + if (env->ExceptionCheck()) { + return; + } + + env->CallStaticVoidMethod(klass, printMethod, l); + if (env->ExceptionCheck()) { + return; + } + if (env->MonitorEnter(l) != 0) { + return; + } + env->CallVoidMethod(run, method); + env->MonitorExit(l); +} + +} // namespace Test1930MonitorInfo +} // namespace art diff --git a/test/1930-monitor-info/run b/test/1930-monitor-info/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/1930-monitor-info/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-run "$@" --jvmti diff --git a/test/1930-monitor-info/src/Main.java b/test/1930-monitor-info/src/Main.java new file mode 100644 index 0000000000..332846133b --- /dev/null +++ b/test/1930-monitor-info/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1930.run(); + } +} diff --git a/test/1930-monitor-info/src/art/Monitors.java b/test/1930-monitor-info/src/art/Monitors.java new file mode 100644 index 0000000000..26f7718dc5 --- /dev/null +++ b/test/1930-monitor-info/src/art/Monitors.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +public class Monitors { + public static class NamedLock { + public final String name; + public NamedLock(String name) { + this.name = name; + } + public String toString() { + return String.format("NamedLock[%s]", name); + } + } + + public static final class MonitorUsage { + public final Object monitor; + public final Thread owner; + public final int entryCount; + public final Thread[] waiters; + public final Thread[] notifyWaiters; + + public MonitorUsage( + Object monitor, + Thread owner, + int entryCount, + Thread[] waiters, + Thread[] notifyWaiters) { + this.monitor = monitor; + this.entryCount = entryCount; + this.owner = owner; + this.waiters = waiters; + this.notifyWaiters = notifyWaiters; + } + + private static String toNameList(Thread[] ts) { + return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); + } + + public String toString() { + return String.format( + "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", + monitor, + (owner != null) ? owner.getName() : "<NULL>", + entryCount, + toNameList(waiters), + toNameList(notifyWaiters)); + } + } + + public static native MonitorUsage getObjectMonitorUsage(Object monitor); +} + diff --git a/test/1930-monitor-info/src/art/Test1930.java b/test/1930-monitor-info/src/art/Test1930.java new file mode 100644 index 0000000000..a7fa1c78cb --- /dev/null +++ b/test/1930-monitor-info/src/art/Test1930.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.util.concurrent.Semaphore; +import java.util.Arrays; + +public class Test1930 { + public static final int NUM_RETRY = 100; + private static void testSingleThread() { + Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testSingleThread"); + executeLocked(() -> { printMonitorUsage(lk); }, lk); + } + private static void testSingleThreadNative() { + Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testSingleThread"); + executeLockedNative(() -> { printMonitorUsage(lk); }, lk); + } + + private static void testLockedTwice() { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwice"); + executeLocked(() -> { executeLocked(() -> { printMonitorUsage(lk); }, lk); }, lk); + } + + private static void testLockedTwiceNJ() { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceNJ"); + executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk); + } + + private static void testLockedTwiceJN() { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceJN"); + executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk); + } + + private static void testLockedTwiceNative() { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceNative"); + executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk); + } + + public final static class ThreadSignaler { + public volatile boolean signal = false; + } + + private static void testLockWait() throws Exception { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockWait"); + final Semaphore sem = new Semaphore(0); + final Thread t = new Thread(() -> { + sem.release(); + synchronized (lk) { + printMonitorUsage(lk); + } + }, "Test1930 Thread - testLockWait"); + synchronized (lk) { + t.start(); + // Wait for the other thread to actually start. + sem.acquire(); + // Wait for the other thread to go to sleep trying to get the mutex. This might take a (short) + // time since we try spinning first for better performance. + boolean found_wait = false; + for (long i = 0; i < NUM_RETRY; i++) { + if (Arrays.asList(Monitors.getObjectMonitorUsage(lk).waiters).contains(t)) { + found_wait = true; + break; + } else { + Thread.sleep(500); + Thread.yield(); + } + } + if (!found_wait) { + System.out.println("other thread doesn't seem to be waiting."); + } + printMonitorUsage(lk); + } + t.join(); + printMonitorUsage(lk); + } + + private static void testNotifyWait() throws Exception { + final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testNotifyWait"); + final Semaphore sem = new Semaphore(0); + Thread t = new Thread(() -> { + synchronized (lk) { + printMonitorUsage(lk); + sem.release(); + try { + lk.wait(); + } catch (Exception e) { + throw new Error("Error waiting!", e); + } + printMonitorUsage(lk); + } + }, "Test1930 Thread - testLockWait"); + t.start(); + sem.acquire(); + synchronized (lk) { + printMonitorUsage(lk); + lk.notifyAll(); + } + t.join(); + printMonitorUsage(lk); + } + + public static void run() throws Exception { + // Single threaded tests. + System.out.println("Running with single thread."); + testSingleThread(); + System.out.println("Running with single thread in native."); + testSingleThreadNative(); + System.out.println("Lock twice"); + testLockedTwice(); + System.out.println("Lock twice native"); + testLockedTwiceNative(); + System.out.println("Lock twice Java then native"); + testLockedTwiceJN(); + System.out.println("Lock twice native then Java"); + testLockedTwiceNJ(); + + // Mutli threaded tests. + System.out.println("lock with wait"); + testLockWait(); + System.out.println("Wait for notify."); + testNotifyWait(); + } + + public static void printPreLock(Object lock) { + System.out.println(String.format("Pre-lock[%s]: %s", + Thread.currentThread().getName(), Monitors.getObjectMonitorUsage(lock))); + } + + public static void executeLocked(Runnable r, Object lock) { + printPreLock(lock); + synchronized (lock) { + r.run(); + } + } + + public native static void executeLockedNative(Runnable r, Object m); + public static void printMonitorUsage(Object m) { + System.out.println(String.format("Thread[%s]: %s", + Thread.currentThread().getName(), Monitors.getObjectMonitorUsage(m))); + } +} diff --git a/test/Android.bp b/test/Android.bp index 2a88af11d6..2f2305607e 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -250,6 +250,7 @@ art_cc_defaults { "ti-agent/common_helper.cc", "ti-agent/frame_pop_helper.cc", "ti-agent/locals_helper.cc", + "ti-agent/monitors_helper.cc", "ti-agent/redefinition_helper.cc", "ti-agent/suspension_helper.cc", "ti-agent/stack_trace_helper.cc", @@ -299,7 +300,8 @@ art_cc_defaults { "1922-owned-monitors-info/owned_monitors.cc", "1924-frame-pop-toggle/frame_pop_toggle.cc", "1926-missed-frame-pop/frame_pop_missed.cc", - "1927-exception-event/exception_event.cc" + "1927-exception-event/exception_event.cc", + "1930-monitor-info/monitor.cc", ], shared_libs: [ "libbase", @@ -349,6 +351,7 @@ art_cc_defaults { ], shared_libs: [ "libbase", + "slicer", ], header_libs: ["libopenjdkjvmti_headers"], } diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 90e260012a..c16c487dd1 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -422,10 +422,7 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then # We really cannot do this on RI so don't both passing it in that case. if [[ "$USE_JVM" = "n" ]]; then - file_1=$(mktemp --tmpdir=${DEX_LOCATION}) - file_2=$(mktemp --tmpdir=${DEX_LOCATION}) - # TODO Remove need for DEXTER_BINARY! - agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}" + agent_args="${agent_args},redefine" fi fi if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index 7280102c6f..c290e9b1ae 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -50,7 +50,7 @@ static const jvmtiCapabilities standard_caps = { .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, - .can_get_monitor_info = 0, + .can_get_monitor_info = 1, .can_pop_frame = 0, .can_redefine_classes = 1, .can_signal_thread = 0, diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc new file mode 100644 index 0000000000..7c28edecd2 --- /dev/null +++ b/test/ti-agent/monitors_helper.cc @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "jni.h" +#include "jvmti.h" +#include <vector> +#include "jvmti_helper.h" +#include "jni_helper.h" +#include "test_env.h" +#include "scoped_local_ref.h" +namespace art { +namespace common_monitors { + +extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getObjectMonitorUsage( + JNIEnv* env, jclass, jobject obj) { + ScopedLocalRef<jclass> klass(env, env->FindClass("art/Monitors$MonitorUsage")); + if (env->ExceptionCheck()) { + return nullptr; + } + jmethodID constructor = env->GetMethodID( + klass.get(), + "<init>", + "(Ljava/lang/Object;Ljava/lang/Thread;I[Ljava/lang/Thread;[Ljava/lang/Thread;)V"); + if (env->ExceptionCheck()) { + return nullptr; + } + jvmtiMonitorUsage usage; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetObjectMonitorUsage(obj, &usage))) { + return nullptr; + } + jobjectArray wait = CreateObjectArray(env, usage.waiter_count, "java/lang/Thread", + [&](jint i) { return usage.waiters[i]; }); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.waiters)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.notify_waiters)); + return nullptr; + } + jobjectArray notify_wait = CreateObjectArray(env, usage.notify_waiter_count, "java/lang/Thread", + [&](jint i) { return usage.notify_waiters[i]; }); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.waiters)); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.notify_waiters)); + return nullptr; + } + return env->NewObject(klass.get(), constructor, + obj, usage.owner, usage.entry_count, wait, notify_wait); +} + +} // namespace common_monitors +} // namespace art diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index 5d7c2f3c60..6e29e36e82 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -28,15 +28,31 @@ #include "jvmti.h" #include "utils.h" +#pragma clang diagnostic push +// slicer defines its own CHECK. b/65422458 +#pragma push_macro("CHECK") +#undef CHECK + +// Slicer's headers have code that triggers these warnings. b/65298177 +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wsign-compare" +#include "code_ir.h" +#include "control_flow_graph.h" +#include "dex_ir.h" +#include "dex_ir_builder.h" +#include "instrumentation.h" +#include "reader.h" +#include "writer.h" + +#pragma pop_macro("CHECK") +#pragma clang diagnostic pop + namespace art { // Should we do a 'full_rewrite' with this test? static constexpr bool kDoFullRewrite = true; struct StressData { - std::string dexter_cmd; - std::string out_temp_dex; - std::string in_temp_dex; bool vm_class_loader_initialized; bool trace_stress; bool redefine_stress; @@ -44,51 +60,60 @@ struct StressData { bool step_stress; }; -static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { - std::ofstream file(fname, std::ios::binary | std::ios::out | std::ios::trunc); - file.write(reinterpret_cast<const char*>(data), data_len); - file.flush(); -} - -static bool ReadIntoBuffer(const std::string& fname, /*out*/std::vector<unsigned char>* data) { - std::ifstream file(fname, std::ios::binary | std::ios::in); - file.seekg(0, std::ios::end); - size_t len = file.tellg(); - data->resize(len); - file.seekg(0); - file.read(reinterpret_cast<char*>(data->data()), len); - return len != 0; -} - -// TODO rewrite later. -static bool DoExtractClassFromData(StressData* data, - const std::string& class_name, +static bool DoExtractClassFromData(jvmtiEnv* env, + const std::string& descriptor, jint in_len, const unsigned char* in_data, - /*out*/std::vector<unsigned char>* dex) { - // Write the dex file into a temporary file. - WriteToFile(data->in_temp_dex, in_len, in_data); - // Clear out file so even if something suppresses the exit value we will still detect dexter - // failure. - WriteToFile(data->out_temp_dex, 0, nullptr); - // Have dexter do the extraction. - std::vector<std::string> args; - args.push_back(data->dexter_cmd); + /*out*/jint* out_len, + /*out*/unsigned char** out_data) { + dex::Reader reader(in_data, in_len); + dex::u4 class_idx = reader.FindClassIndex(descriptor.c_str()); + if (class_idx != dex::kNoIndex) { + reader.CreateClassIr(class_idx); + } else { + LOG(ERROR) << "ERROR: Can't find class " << descriptor; + return false; + } + auto dex_ir = reader.GetIr(); + if (kDoFullRewrite) { - args.push_back("-x"); - args.push_back("full_rewrite"); - } - args.push_back("-e"); - args.push_back(class_name); - args.push_back("-o"); - args.push_back(data->out_temp_dex); - args.push_back(data->in_temp_dex); - std::string error; - if (ExecAndReturnCode(args, &error) != 0) { - LOG(ERROR) << "unable to execute dexter: " << error; + for (auto& ir_method : dex_ir->encoded_methods) { + if (ir_method->code != nullptr) { + lir::CodeIr code_ir(ir_method.get(), dex_ir); + lir::ControlFlowGraph cfg_compact(&code_ir, false); + lir::ControlFlowGraph cfg_verbose(&code_ir, true); + code_ir.Assemble(); + } + } + } + dex::Writer writer(dex_ir); + + struct Allocator : public dex::Writer::Allocator { + explicit Allocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {} + virtual void* Allocate(size_t size) { + unsigned char* out = nullptr; + if (JVMTI_ERROR_NONE != jvmti_env_->Allocate(size, &out)) { + return nullptr; + } else { + return out; + } + } + virtual void Free(void* ptr) { + jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(ptr)); + } + private: + jvmtiEnv* jvmti_env_; + }; + Allocator alloc(env); + size_t res_len; + unsigned char* res = writer.CreateImage(&alloc, &res_len); + if (res != nullptr) { + *out_data = res; + *out_len = res_len; + return true; + } else { return false; } - return ReadIntoBuffer(data->out_temp_dex, dex); } class ScopedThreadInfo { @@ -615,10 +640,10 @@ void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, jint* new_class_data_len, unsigned char** new_class_data) { std::vector<unsigned char> out; - std::string name_str(name); - // Make the jvmti semi-descriptor into the java style descriptor (though with $ for inner - // classes). - std::replace(name_str.begin(), name_str.end(), '/', '.'); + // Make the jvmti semi-descriptor into the full descriptor. + std::string name_str("L"); + name_str += name; + name_str += ";"; StressData* data = nullptr; CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)), JVMTI_ERROR_NONE); @@ -626,15 +651,11 @@ void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, LOG(WARNING) << "Ignoring load of class " << name << " because VMClassLoader is not yet " << "initialized. Transforming this class could cause spurious test failures."; return; - } else if (DoExtractClassFromData(data, name_str, class_data_len, class_data, /*out*/ &out)) { + } else if (DoExtractClassFromData(jvmti, name_str, class_data_len, class_data, + /*out*/ new_class_data_len, /*out*/ new_class_data)) { LOG(INFO) << "Extracted class: " << name; - unsigned char* new_data; - CHECK_EQ(JVMTI_ERROR_NONE, jvmti->Allocate(out.size(), &new_data)); - memcpy(new_data, out.data(), out.size()); - *new_class_data_len = static_cast<jint>(out.size()); - *new_class_data = new_data; } else { - std::cerr << "Unable to extract class " << name_str << std::endl; + std::cerr << "Unable to extract class " << name << std::endl; *new_class_data_len = 0; *new_class_data = nullptr; } @@ -653,7 +674,7 @@ static std::string GetOption(const std::string& in) { } // Options are -// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace,][field] +// jvmti-stress,[redefine,][trace,][field] static void ReadOptions(StressData* data, char* options) { std::string ops(options); CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress"; @@ -668,12 +689,6 @@ static void ReadOptions(StressData* data, char* options) { data->field_stress = true; } else if (cur == "redefine") { data->redefine_stress = true; - ops = AdvanceOption(ops); - data->dexter_cmd = GetOption(ops); - ops = AdvanceOption(ops); - data->in_temp_dex = GetOption(ops); - ops = AdvanceOption(ops); - data->out_temp_dex = GetOption(ops); } else { LOG(FATAL) << "Unknown option: " << GetOption(ops); } diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt index 5806b6107f..d27b8fc06c 100644 --- a/tools/libcore_gcstress_debug_failures.txt +++ b/tools/libcore_gcstress_debug_failures.txt @@ -11,9 +11,11 @@ names: ["jsr166.CompletableFutureTest#testCompleteOnTimeout_completed", "libcore.icu.TransliteratorTest#testAll", "libcore.icu.RelativeDateTimeFormatterTest#test_bug25821045", + "libcore.icu.RelativeDateTimeFormatterTest#test_bug25883157", "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndTimeout", "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndNoTimeout", "libcore.java.util.TimeZoneTest#testSetDefaultDeadlock", + "libcore.javax.crypto.CipherBasicsTest#testBasicEncryption", "org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread"] } ] |