diff options
119 files changed, 4668 insertions, 925 deletions
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc index 8eb37cf3dc..0355f116f1 100644 --- a/compiler/dex/verified_method.cc +++ b/compiler/dex/verified_method.cc @@ -313,8 +313,9 @@ void VerifiedMethod::GenerateDevirtMap(verifier::MethodVerifier* method_verifier concrete_method = reg_type.GetClass()->FindVirtualMethodForVirtual( abstract_method, pointer_size); } - if (concrete_method == nullptr || concrete_method->IsAbstract()) { - // In cases where concrete_method is not found, or is abstract, continue to the next invoke. + if (concrete_method == nullptr || !concrete_method->IsInvokable()) { + // In cases where concrete_method is not found, or is not invokable, continue to the next + // invoke. continue; } if (reg_type.IsPreciseReference() || concrete_method->IsFinal() || diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 14ba81d193..10841e6700 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -329,7 +329,7 @@ inline int CompilerDriver::IsFastInvoke( resolved_method->GetMethodIndex() < methods_class->GetVTableLength() && (methods_class->GetVTableEntry( resolved_method->GetMethodIndex(), pointer_size) == resolved_method) && - !resolved_method->IsAbstract(); + resolved_method->IsInvokable(); if (can_sharpen_virtual_based_on_type || can_sharpen_super_based_on_type) { // Sharpen a virtual call into a direct call. The method_idx is into referrer's @@ -374,7 +374,7 @@ inline int CompilerDriver::IsFastInvoke( class_loader, nullptr, kVirtual); } CHECK(called_method != nullptr); - CHECK(!called_method->IsAbstract()); + CHECK(called_method->IsInvokable()); int stats_flags = kFlagMethodResolved; GetCodeAndMethodForDirectCall(/*out*/invoke_type, kDirect, // Sharp type diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index aa5e411ba8..bf3a8658da 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -1552,7 +1552,7 @@ void CompilerDriver::GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType *type = sharp_type; } } else { - auto* image_space = heap->GetImageSpace(); + auto* image_space = heap->GetBootImageSpace(); bool method_in_image = false; if (image_space != nullptr) { const auto& method_section = image_space->GetImageHeader().GetMethodsSection(); @@ -2034,8 +2034,14 @@ class VerifyClassVisitor : public CompilationVisitor { Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache( soa.Self(), dex_file, false))); std::string error_msg; - if (verifier::MethodVerifier::VerifyClass(soa.Self(), &dex_file, dex_cache, class_loader, - &class_def, true, &error_msg) == + if (verifier::MethodVerifier::VerifyClass(soa.Self(), + &dex_file, + dex_cache, + class_loader, + &class_def, + true /* allow soft failures */, + true /* log hard failures */, + &error_msg) == verifier::MethodVerifier::kHardFailure) { LOG(ERROR) << "Verification failed on class " << PrettyDescriptor(descriptor) << " because: " << error_msg; diff --git a/compiler/elf_writer_debug.cc b/compiler/elf_writer_debug.cc index 90db7eb41a..73262333b6 100644 --- a/compiler/elf_writer_debug.cc +++ b/compiler/elf_writer_debug.cc @@ -451,12 +451,12 @@ void WriteDebugSections(ElfBuilder<ElfTypes>* builder, opcodes.EndSequence(); WriteDebugLineTable(directories, files, opcodes, &debug_line, &debug_line_patches); } + builder->WriteSection(".debug_line", &debug_line); + builder->WritePatches(".debug_line.oat_patches", &debug_line_patches); builder->WriteSection(".debug_info", &debug_info); builder->WritePatches(".debug_info.oat_patches", &debug_info_patches); builder->WriteSection(".debug_abbrev", &debug_abbrev); builder->WriteSection(".debug_str", &debug_str); - builder->WriteSection(".debug_line", &debug_line); - builder->WritePatches(".debug_line.oat_patches", &debug_line_patches); } // Explicit instantiations diff --git a/compiler/image_test.cc b/compiler/image_test.cc index a38e1f54c0..6df15279a0 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -181,7 +181,7 @@ TEST_F(ImageTest, WriteRead) { ASSERT_TRUE(heap->HasImageSpace()); ASSERT_TRUE(heap->GetNonMovingSpace()->IsMallocSpace()); - gc::space::ImageSpace* image_space = heap->GetImageSpace(); + gc::space::ImageSpace* image_space = heap->GetBootImageSpace(); ASSERT_TRUE(image_space != nullptr); ASSERT_LE(image_space->Size(), image_file_size); diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 0c85323805..3f18d9aa0f 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -372,9 +372,9 @@ void ImageWriter::AddMethodPointerArray(mirror::PointerArray* arr) { DCHECK(arr != nullptr); if (kIsDebugBuild) { for (size_t i = 0, len = arr->GetLength(); i < len; i++) { - auto* method = arr->GetElementPtrSize<ArtMethod*>(i, target_ptr_size_); + ArtMethod* method = arr->GetElementPtrSize<ArtMethod*>(i, target_ptr_size_); if (method != nullptr && !method->IsRuntimeMethod()) { - auto* klass = method->GetDeclaringClass(); + mirror::Class* klass = method->GetDeclaringClass(); CHECK(klass == nullptr || KeepClass(klass)) << PrettyClass(klass) << " should be a kept class"; } @@ -514,7 +514,7 @@ bool ImageWriter::IsImageBinSlotAssigned(mirror::Object* object) const { size_t offset = lock_word.ForwardingAddress(); BinSlot bin_slot(offset); DCHECK_LT(bin_slot.GetIndex(), bin_slot_sizes_[bin_slot.GetBin()]) - << "bin slot offset should not exceed the size of that bin"; + << "bin slot offset should not exceed the size of that bin"; } return true; } @@ -537,8 +537,13 @@ bool ImageWriter::AllocMemory() { const size_t length = RoundUp(image_objects_offset_begin_ + GetBinSizeSum() + intern_table_bytes_, kPageSize); std::string error_msg; - image_.reset(MemMap::MapAnonymous("image writer image", nullptr, length, PROT_READ | PROT_WRITE, - false, false, &error_msg)); + image_.reset(MemMap::MapAnonymous("image writer image", + nullptr, + length, + PROT_READ | PROT_WRITE, + false, + false, + &error_msg)); if (UNLIKELY(image_.get() == nullptr)) { LOG(ERROR) << "Failed to allocate memory for image file generation: " << error_msg; return false; @@ -547,7 +552,9 @@ bool ImageWriter::AllocMemory() { // Create the image bitmap, only needs to cover mirror object section which is up to image_end_. CHECK_LE(image_end_, length); image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create( - "image bitmap", image_->Begin(), RoundUp(image_end_, kPageSize))); + "image bitmap", + image_->Begin(), + RoundUp(image_end_, kPageSize))); if (image_bitmap_.get() == nullptr) { LOG(ERROR) << "Failed to allocate memory for image bitmap"; return false; @@ -905,8 +912,8 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { size_t& offset = bin_slot_sizes_[kBinArtField]; DCHECK(!IsInBootImage(cur_fields)); native_object_relocations_.emplace( - cur_fields, NativeObjectRelocation { - offset, kNativeObjectRelocationTypeArtFieldArray }); + cur_fields, + NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtFieldArray }); offset += header_size; // Forward individual fields so that we can quickly find where they belong. for (size_t i = 0, count = cur_fields->size(); i < count; ++i) { @@ -917,7 +924,8 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { << " already assigned " << PrettyField(field) << " static=" << field->IsStatic(); DCHECK(!IsInBootImage(field)); native_object_relocations_.emplace( - field, NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtField }); + field, + NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtField }); offset += sizeof(ArtField); } } @@ -940,8 +948,9 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { any_dirty = any_dirty || WillMethodBeDirty(&m); ++count; } - NativeObjectRelocationType type = any_dirty ? kNativeObjectRelocationTypeArtMethodDirty : - kNativeObjectRelocationTypeArtMethodClean; + NativeObjectRelocationType type = any_dirty + ? kNativeObjectRelocationTypeArtMethodDirty + : kNativeObjectRelocationTypeArtMethodClean; Bin bin_type = BinTypeForNativeRelocationType(type); // Forward the entire array at once, but header first. const size_t header_size = LengthPrefixedArray<ArtMethod>::ComputeSize(0, @@ -1124,8 +1133,9 @@ void ImageWriter::CreateHeader(size_t oat_loaded_size, size_t oat_data_offset) { cur_pos = RoundUp(cur_pos, ArtMethod::Alignment(target_ptr_size_)); // Add method section. auto* methods_section = §ions[ImageHeader::kSectionArtMethods]; - *methods_section = ImageSection(cur_pos, bin_slot_sizes_[kBinArtMethodClean] + - bin_slot_sizes_[kBinArtMethodDirty]); + *methods_section = ImageSection(cur_pos, + bin_slot_sizes_[kBinArtMethodClean] + + bin_slot_sizes_[kBinArtMethodDirty]); CHECK_EQ(bin_slot_offsets_[kBinArtMethodClean], methods_section->Offset()); cur_pos = methods_section->End(); // Add dex cache arrays section. @@ -1156,12 +1166,17 @@ void ImageWriter::CreateHeader(size_t oat_loaded_size, size_t oat_data_offset) { CHECK_EQ(AlignUp(image_begin_ + image_end, kPageSize), oat_file_begin) << "Oat file should be right after the image."; // Create the header. - new (image_->Begin()) ImageHeader( - PointerToLowMemUInt32(image_begin_), image_end, - sections, image_roots_address_, oat_file_->GetOatHeader().GetChecksum(), - PointerToLowMemUInt32(oat_file_begin), PointerToLowMemUInt32(oat_data_begin_), - PointerToLowMemUInt32(oat_data_end), PointerToLowMemUInt32(oat_file_end), target_ptr_size_, - compile_pic_); + new (image_->Begin()) ImageHeader(PointerToLowMemUInt32(image_begin_), + image_end, + sections, + image_roots_address_, + oat_file_->GetOatHeader().GetChecksum(), + PointerToLowMemUInt32(oat_file_begin), + PointerToLowMemUInt32(oat_data_begin_), + PointerToLowMemUInt32(oat_data_end), + PointerToLowMemUInt32(oat_file_end), + target_ptr_size_, + compile_pic_); } ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) { @@ -1371,14 +1386,16 @@ class FixupVisitor { // Use SetFieldObjectWithoutWriteBarrier to avoid card marking since we are writing to the // image. copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>( - offset, image_writer_->GetImageAddress(ref)); + offset, + image_writer_->GetImageAddress(ref)); } // java.lang.ref.Reference visitor. void operator()(mirror::Class* klass ATTRIBUTE_UNUSED, mirror::Reference* ref) const SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>( - mirror::Reference::ReferentOffset(), image_writer_->GetImageAddress(ref->GetReferent())); + mirror::Reference::ReferentOffset(), + image_writer_->GetImageAddress(ref->GetReferent())); } protected: @@ -1572,7 +1589,7 @@ const uint8_t* ImageWriter::GetOatAddress(OatAddress type) const { // If we are compiling an app image, we need to use the stubs of the boot image. if (compile_app_image_) { // Use the current image pointers. - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); DCHECK(image_space != nullptr); const OatFile* oat_file = image_space->GetOatFile(); CHECK(oat_file != nullptr); @@ -1604,7 +1621,7 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_inter DCHECK(!method->IsResolutionMethod()) << PrettyMethod(method); DCHECK(!method->IsImtConflictMethod()) << PrettyMethod(method); DCHECK(!method->IsImtUnimplementedMethod()) << PrettyMethod(method); - DCHECK(!method->IsAbstract()) << PrettyMethod(method); + DCHECK(method->IsInvokable()) << PrettyMethod(method); DCHECK(!IsInBootImage(method)) << PrettyMethod(method); // Use original code if it exists. Otherwise, set the code pointer to the resolution @@ -1651,7 +1668,7 @@ const uint8_t* ImageWriter::GetQuickEntryPoint(ArtMethod* method) { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. - if (UNLIKELY(method->IsAbstract())) { + if (UNLIKELY(!method->IsInvokable())) { return GetOatAddress(kOatAddressQuickToInterpreterBridge); } else { bool quick_is_interpreted; @@ -1697,7 +1714,7 @@ void ImageWriter::CopyAndFixupMethod(ArtMethod* orig, ArtMethod* copy) { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. - if (UNLIKELY(orig->IsAbstract())) { + if (UNLIKELY(!orig->IsInvokable())) { copy->SetEntryPointFromQuickCompiledCodePtrSize( GetOatAddress(kOatAddressQuickToInterpreterBridge), target_ptr_size_); } else { @@ -1727,8 +1744,10 @@ static OatHeader* GetOatHeaderFromElf(ElfFile* elf) { void ImageWriter::SetOatChecksumFromElfFile(File* elf_file) { std::string error_msg; - std::unique_ptr<ElfFile> elf(ElfFile::Open(elf_file, PROT_READ|PROT_WRITE, - MAP_SHARED, &error_msg)); + std::unique_ptr<ElfFile> elf(ElfFile::Open(elf_file, + PROT_READ | PROT_WRITE, + MAP_SHARED, + &error_msg)); if (elf.get() == nullptr) { LOG(FATAL) << "Unable open oat file: " << error_msg; return; @@ -1771,10 +1790,11 @@ uint32_t ImageWriter::BinSlot::GetIndex() const { uint8_t* ImageWriter::GetOatFileBegin() const { DCHECK_GT(intern_table_bytes_, 0u); - size_t native_sections_size = - bin_slot_sizes_[kBinArtField] + bin_slot_sizes_[kBinArtMethodDirty] + - bin_slot_sizes_[kBinArtMethodClean] + bin_slot_sizes_[kBinDexCacheArray] + - intern_table_bytes_; + size_t native_sections_size = bin_slot_sizes_[kBinArtField] + + bin_slot_sizes_[kBinArtMethodDirty] + + bin_slot_sizes_[kBinArtMethodClean] + + bin_slot_sizes_[kBinDexCacheArray] + + intern_table_bytes_; return image_begin_ + RoundUp(image_end_ + native_sections_size, kPageSize); } diff --git a/compiler/image_writer.h b/compiler/image_writer.h index 120de97620..a0a785e21c 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -308,8 +308,11 @@ class ImageWriter FINAL { SHARED_REQUIRES(Locks::mutator_lock_); void FixupDexCache(mirror::DexCache* orig_dex_cache, mirror::DexCache* copy_dex_cache) SHARED_REQUIRES(Locks::mutator_lock_); - void FixupPointerArray(mirror::Object* dst, mirror::PointerArray* arr, mirror::Class* klass, - Bin array_type) SHARED_REQUIRES(Locks::mutator_lock_); + void FixupPointerArray(mirror::Object* dst, + mirror::PointerArray* arr, + mirror::Class* klass, + Bin array_type) + SHARED_REQUIRES(Locks::mutator_lock_); // Get quick code for non-resolution/imt_conflict/abstract method. const uint8_t* GetQuickCode(ArtMethod* method, bool* quick_is_interpreted) @@ -331,8 +334,12 @@ class ImageWriter FINAL { void AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type) SHARED_REQUIRES(Locks::mutator_lock_); + // Return true if klass is loaded by the boot class loader but not in the boot image. bool IsBootClassLoaderNonImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); + // Return true if klass depends on a boot class loader non image class live. We want to prune + // these classes since we do not want any boot class loader classes in the image. This means that + // we also cannot have any classes which refer to these boot class loader non image classes. bool ContainsBootClassLoaderNonImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_); @@ -394,7 +401,7 @@ class ImageWriter FINAL { const bool compile_pic_; const bool compile_app_image_; - // Boot image space for fast lookups. + // Cache the boot image space in this class for faster lookups. gc::space::ImageSpace* boot_image_space_; // Size of pointers on the target architecture. @@ -432,7 +439,7 @@ class ImageWriter FINAL { uint64_t dirty_methods_; uint64_t clean_methods_; - // Prune class memoization table. + // Prune class memoization table to speed up ContainsBootClassLoaderNonImageClass. std::unordered_map<mirror::Class*, bool> prune_class_memo_; friend class ContainsBootClassLoaderNonImageClassVisitor; diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 3f2271ef11..40a3f14f93 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -899,7 +899,7 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { // NOTE: We're using linker patches for app->boot references when the image can // be relocated and therefore we need to emit .oat_patches. We're not using this // for app->app references, so check that the method is an image method. - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); size_t method_offset = reinterpret_cast<const uint8_t*>(method) - image_space->Begin(); CHECK(image_space->GetImageHeader().GetMethodsSection().Contains(method_offset)); } diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 6d05293277..54c6cc8890 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -2694,7 +2694,7 @@ void LocationsBuilderARM::VisitDiv(HDiv* div) { case Primitive::kPrimInt: { if (div->InputAt(1)->IsConstant()) { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrConstant(div->InputAt(1))); + locations->SetInAt(1, Location::ConstantLocation(div->InputAt(1)->AsConstant())); locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); int32_t abs_imm = std::abs(div->InputAt(1)->AsIntConstant()->GetValue()); if (abs_imm <= 1) { @@ -2818,7 +2818,7 @@ void LocationsBuilderARM::VisitRem(HRem* rem) { case Primitive::kPrimInt: { if (rem->InputAt(1)->IsConstant()) { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrConstant(rem->InputAt(1))); + locations->SetInAt(1, Location::ConstantLocation(rem->InputAt(1)->AsConstant())); locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); int32_t abs_imm = std::abs(rem->InputAt(1)->AsIntConstant()->GetValue()); if (abs_imm <= 1) { @@ -2989,17 +2989,29 @@ void LocationsBuilderARM::HandleShift(HBinaryOperation* op) { switch (op->GetResultType()) { case Primitive::kPrimInt: { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RegisterOrConstant(op->InputAt(1))); - // Make the output overlap, as it will be used to hold the masked - // second input. - locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + if (op->InputAt(1)->IsConstant()) { + locations->SetInAt(1, Location::ConstantLocation(op->InputAt(1)->AsConstant())); + locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap); + } else { + locations->SetInAt(1, Location::RequiresRegister()); + // Make the output overlap, as it will be used to hold the masked + // second input. + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + } break; } case Primitive::kPrimLong: { locations->SetInAt(0, Location::RequiresRegister()); - locations->SetInAt(1, Location::RequiresRegister()); - locations->AddTemp(Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister()); + if (op->InputAt(1)->IsConstant()) { + locations->SetInAt(1, Location::ConstantLocation(op->InputAt(1)->AsConstant())); + // For simplicity, use kOutputOverlap even though we only require that low registers + // don't clash with high registers which the register allocator currently guarantees. + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + } else { + locations->SetInAt(1, Location::RequiresRegister()); + locations->AddTemp(Location::RequiresRegister()); + locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap); + } break; } default: @@ -3020,9 +3032,9 @@ void InstructionCodeGeneratorARM::HandleShift(HBinaryOperation* op) { case Primitive::kPrimInt: { Register out_reg = out.AsRegister<Register>(); Register first_reg = first.AsRegister<Register>(); - // Arm doesn't mask the shift count so we need to do it ourselves. if (second.IsRegister()) { Register second_reg = second.AsRegister<Register>(); + // Arm doesn't mask the shift count so we need to do it ourselves. __ and_(out_reg, second_reg, ShifterOperand(kMaxIntShiftValue)); if (op->IsShl()) { __ Lsl(out_reg, first_reg, out_reg); @@ -3050,57 +3062,103 @@ void InstructionCodeGeneratorARM::HandleShift(HBinaryOperation* op) { Register o_h = out.AsRegisterPairHigh<Register>(); Register o_l = out.AsRegisterPairLow<Register>(); - Register temp = locations->GetTemp(0).AsRegister<Register>(); - Register high = first.AsRegisterPairHigh<Register>(); Register low = first.AsRegisterPairLow<Register>(); - Register second_reg = second.AsRegister<Register>(); - - if (op->IsShl()) { - __ and_(o_l, second_reg, ShifterOperand(kMaxLongShiftValue)); - // Shift the high part - __ Lsl(o_h, high, o_l); - // Shift the low part and `or` what overflew on the high part - __ rsb(temp, o_l, ShifterOperand(kArmBitsPerWord)); - __ Lsr(temp, low, temp); - __ orr(o_h, o_h, ShifterOperand(temp)); - // If the shift is > 32 bits, override the high part - __ subs(temp, o_l, ShifterOperand(kArmBitsPerWord)); - __ it(PL); - __ Lsl(o_h, low, temp, PL); - // Shift the low part - __ Lsl(o_l, low, o_l); - } else if (op->IsShr()) { - __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue)); - // Shift the low part - __ Lsr(o_l, low, o_h); - // Shift the high part and `or` what underflew on the low part - __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord)); - __ Lsl(temp, high, temp); - __ orr(o_l, o_l, ShifterOperand(temp)); - // If the shift is > 32 bits, override the low part - __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord)); - __ it(PL); - __ Asr(o_l, high, temp, PL); - // Shift the high part - __ Asr(o_h, high, o_h); + if (second.IsRegister()) { + Register temp = locations->GetTemp(0).AsRegister<Register>(); + + Register second_reg = second.AsRegister<Register>(); + + if (op->IsShl()) { + __ and_(o_l, second_reg, ShifterOperand(kMaxLongShiftValue)); + // Shift the high part + __ Lsl(o_h, high, o_l); + // Shift the low part and `or` what overflew on the high part + __ rsb(temp, o_l, ShifterOperand(kArmBitsPerWord)); + __ Lsr(temp, low, temp); + __ orr(o_h, o_h, ShifterOperand(temp)); + // If the shift is > 32 bits, override the high part + __ subs(temp, o_l, ShifterOperand(kArmBitsPerWord)); + __ it(PL); + __ Lsl(o_h, low, temp, PL); + // Shift the low part + __ Lsl(o_l, low, o_l); + } else if (op->IsShr()) { + __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue)); + // Shift the low part + __ Lsr(o_l, low, o_h); + // Shift the high part and `or` what underflew on the low part + __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord)); + __ Lsl(temp, high, temp); + __ orr(o_l, o_l, ShifterOperand(temp)); + // If the shift is > 32 bits, override the low part + __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord)); + __ it(PL); + __ Asr(o_l, high, temp, PL); + // Shift the high part + __ Asr(o_h, high, o_h); + } else { + __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue)); + // same as Shr except we use `Lsr`s and not `Asr`s + __ Lsr(o_l, low, o_h); + __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord)); + __ Lsl(temp, high, temp); + __ orr(o_l, o_l, ShifterOperand(temp)); + __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord)); + __ it(PL); + __ Lsr(o_l, high, temp, PL); + __ Lsr(o_h, high, o_h); + } } else { - __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue)); - // same as Shr except we use `Lsr`s and not `Asr`s - __ Lsr(o_l, low, o_h); - __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord)); - __ Lsl(temp, high, temp); - __ orr(o_l, o_l, ShifterOperand(temp)); - __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord)); - __ it(PL); - __ Lsr(o_l, high, temp, PL); - __ Lsr(o_h, high, o_h); + // Register allocator doesn't create partial overlap. + DCHECK_NE(o_l, high); + DCHECK_NE(o_h, low); + int32_t cst = second.GetConstant()->AsIntConstant()->GetValue(); + uint32_t shift_value = static_cast<uint32_t>(cst & kMaxLongShiftValue); + if (shift_value > 32) { + if (op->IsShl()) { + __ Lsl(o_h, low, shift_value - 32); + __ LoadImmediate(o_l, 0); + } else if (op->IsShr()) { + __ Asr(o_l, high, shift_value - 32); + __ Asr(o_h, high, 31); + } else { + __ Lsr(o_l, high, shift_value - 32); + __ LoadImmediate(o_h, 0); + } + } else if (shift_value == 32) { + if (op->IsShl()) { + __ mov(o_h, ShifterOperand(low)); + __ LoadImmediate(o_l, 0); + } else if (op->IsShr()) { + __ mov(o_l, ShifterOperand(high)); + __ Asr(o_h, high, 31); + } else { + __ mov(o_l, ShifterOperand(high)); + __ LoadImmediate(o_h, 0); + } + } else { // shift_value < 32 + if (op->IsShl()) { + __ Lsl(o_h, high, shift_value); + __ orr(o_h, o_h, ShifterOperand(low, LSR, 32 - shift_value)); + __ Lsl(o_l, low, shift_value); + } else if (op->IsShr()) { + __ Lsr(o_l, low, shift_value); + __ orr(o_l, o_l, ShifterOperand(high, LSL, 32 - shift_value)); + __ Asr(o_h, high, shift_value); + } else { + __ Lsr(o_l, low, shift_value); + __ orr(o_l, o_l, ShifterOperand(high, LSL, 32 - shift_value)); + __ Lsr(o_h, high, shift_value); + } + } } break; } default: LOG(FATAL) << "Unexpected operation type " << type; + UNREACHABLE(); } } diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index b36a04216d..9b78dec6c4 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -2977,7 +2977,7 @@ void LocationsBuilderMIPS64::VisitLoadClass(HLoadClass* cls) { CodeGenerator::CreateLoadClassLocationSummary( cls, Location::RegisterLocation(calling_convention.GetRegisterAt(0)), - Location::RegisterLocation(A0)); + calling_convention.GetReturnLocation(cls->GetType())); } void InstructionCodeGeneratorMIPS64::VisitLoadClass(HLoadClass* cls) { diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index 58c6e0fa83..ac3162f736 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -119,9 +119,12 @@ class FieldAccessCallingConventionMIPS64 : public FieldAccessCallingConvention { Location GetReturnLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::RegisterLocation(V0); } - Location GetSetValueLocation( - Primitive::Type type ATTRIBUTE_UNUSED, bool is_instance) const OVERRIDE { - return is_instance ? Location::RegisterLocation(A2) : Location::RegisterLocation(A1); + Location GetSetValueLocation(Primitive::Type type, bool is_instance) const OVERRIDE { + return Primitive::Is64BitType(type) + ? Location::RegisterLocation(A2) + : (is_instance + ? Location::RegisterLocation(A2) + : Location::RegisterLocation(A1)); } Location GetFpuLocation(Primitive::Type type ATTRIBUTE_UNUSED) const OVERRIDE { return Location::FpuRegisterLocation(F0); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index d5d6c210bf..0147b010f2 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -3024,7 +3024,7 @@ void InstructionCodeGeneratorX86::GenerateDivRemIntegral(HBinaryOperation* instr DCHECK_EQ(EAX, first.AsRegister<Register>()); DCHECK_EQ(is_div ? EAX : EDX, out.AsRegister<Register>()); - if (instruction->InputAt(1)->IsIntConstant()) { + if (second.IsConstant()) { int32_t imm = second.GetConstant()->AsIntConstant()->GetValue(); if (imm == 0) { diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 9754043f32..02e5dab3d4 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -123,20 +123,21 @@ void HDeadCodeElimination::RemoveDeadBlocks() { } // If we removed at least one block, we need to recompute the full - // dominator tree. + // dominator tree and try block membership. if (removed_one_or_more_blocks) { graph_->ClearDominanceInformation(); graph_->ComputeDominanceInformation(); + graph_->ComputeTryBlockInformation(); } // Connect successive blocks created by dead branches. Order does not matter. for (HReversePostOrderIterator it(*graph_); !it.Done();) { HBasicBlock* block = it.Current(); - if (block->IsEntryBlock() || block->GetSuccessors().size() != 1u) { + if (block->IsEntryBlock() || !block->GetLastInstruction()->IsGoto()) { it.Advance(); continue; } - HBasicBlock* successor = block->GetSuccessors()[0]; + HBasicBlock* successor = block->GetSingleSuccessor(); if (successor->IsExitBlock() || successor->GetPredecessors().size() != 1u) { it.Advance(); continue; @@ -176,10 +177,7 @@ void HDeadCodeElimination::RemoveDeadInstructions() { } void HDeadCodeElimination::Run() { - if (!graph_->HasTryCatch()) { - // TODO: Update dead block elimination and enable for try/catch. - RemoveDeadBlocks(); - } + RemoveDeadBlocks(); SsaRedundantPhiElimination(graph_).Run(); RemoveDeadInstructions(); } diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index 0d7c796837..5814d7556f 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -163,12 +163,12 @@ void GraphChecker::VisitBoundsCheck(HBoundsCheck* check) { } void GraphChecker::VisitTryBoundary(HTryBoundary* try_boundary) { - // Ensure that all exception handlers are catch blocks and that handlers - // are not listed multiple times. + ArrayRef<HBasicBlock* const> handlers = try_boundary->GetExceptionHandlers(); + + // Ensure that all exception handlers are catch blocks. // Note that a normal-flow successor may be a catch block before CFG // simplification. We only test normal-flow successors in SsaChecker. - for (HExceptionHandlerIterator it(*try_boundary); !it.Done(); it.Advance()) { - HBasicBlock* handler = it.Current(); + for (HBasicBlock* handler : handlers) { if (!handler->IsCatchBlock()) { AddError(StringPrintf("Block %d with %s:%d has exceptional successor %d which " "is not a catch block.", @@ -177,9 +177,13 @@ void GraphChecker::VisitTryBoundary(HTryBoundary* try_boundary) { try_boundary->GetId(), handler->GetBlockId())); } - if (current_block_->HasSuccessor(handler, it.CurrentSuccessorIndex() + 1)) { - AddError(StringPrintf("Exception handler block %d of %s:%d is listed multiple times.", - handler->GetBlockId(), + } + + // Ensure that handlers are not listed multiple times. + for (size_t i = 0, e = handlers.size(); i < e; ++i) { + if (ContainsElement(handlers, handlers[i], i + 1)) { + AddError(StringPrintf("Exception handler block %d of %s:%d is listed multiple times.", + handlers[i]->GetBlockId(), try_boundary->DebugName(), try_boundary->GetId())); } @@ -371,17 +375,14 @@ void SSAChecker::VisitBasicBlock(HBasicBlock* block) { // Ensure that catch blocks are not normal successors, and normal blocks are // never exceptional successors. - const size_t num_normal_successors = block->NumberOfNormalSuccessors(); - for (size_t j = 0; j < num_normal_successors; ++j) { - HBasicBlock* successor = block->GetSuccessors()[j]; + for (HBasicBlock* successor : block->GetNormalSuccessors()) { if (successor->IsCatchBlock()) { AddError(StringPrintf("Catch block %d is a normal successor of block %d.", successor->GetBlockId(), block->GetBlockId())); } } - for (size_t j = num_normal_successors, e = block->GetSuccessors().size(); j < e; ++j) { - HBasicBlock* successor = block->GetSuccessors()[j]; + for (HBasicBlock* successor : block->GetExceptionalSuccessors()) { if (!successor->IsCatchBlock()) { AddError(StringPrintf("Normal block %d is an exceptional successor of block %d.", successor->GetBlockId(), @@ -393,10 +394,14 @@ void SSAChecker::VisitBasicBlock(HBasicBlock* block) { // block with multiple successors to a block with multiple // predecessors). Exceptional edges are synthesized and hence // not accounted for. - if (block->NumberOfNormalSuccessors() > 1) { - for (size_t j = 0, e = block->NumberOfNormalSuccessors(); j < e; ++j) { - HBasicBlock* successor = block->GetSuccessors()[j]; - if (successor->GetPredecessors().size() > 1) { + if (block->GetSuccessors().size() > 1) { + for (HBasicBlock* successor : block->GetNormalSuccessors()) { + if (successor->IsExitBlock() && + block->IsSingleTryBoundary() && + block->GetPredecessors().size() == 1u && + block->GetSinglePredecessor()->GetLastInstruction()->IsThrow()) { + // Allowed critical edge Throw->TryBoundary->Exit. + } else if (successor->GetPredecessors().size() > 1) { AddError(StringPrintf("Critical edge between blocks %d and %d.", block->GetBlockId(), successor->GetBlockId())); diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 4111671a9b..2b7790184a 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -24,6 +24,7 @@ #include "code_generator.h" #include "dead_code_elimination.h" #include "disassembler.h" +#include "inliner.h" #include "licm.h" #include "nodes.h" #include "optimization.h" @@ -252,8 +253,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { void PrintSuccessors(HBasicBlock* block) { AddIndent(); output_ << "successors"; - for (size_t i = 0; i < block->NumberOfNormalSuccessors(); ++i) { - HBasicBlock* successor = block->GetSuccessors()[i]; + for (HBasicBlock* successor : block->GetNormalSuccessors()) { output_ << " \"B" << successor->GetBlockId() << "\" "; } output_<< std::endl; @@ -262,8 +262,7 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { void PrintExceptionHandlers(HBasicBlock* block) { AddIndent(); output_ << "xhandlers"; - for (size_t i = block->NumberOfNormalSuccessors(); i < block->GetSuccessors().size(); ++i) { - HBasicBlock* handler = block->GetSuccessors()[i]; + for (HBasicBlock* handler : block->GetExceptionalSuccessors()) { output_ << " \"B" << handler->GetBlockId() << "\" "; } if (block->IsExitBlock() && @@ -424,11 +423,6 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { return strcmp(pass_name_, name) == 0; } - bool IsReferenceTypePropagationPass() { - return strstr(pass_name_, ReferenceTypePropagation::kReferenceTypePropagationPassName) - != nullptr; - } - void PrintInstruction(HInstruction* instruction) { output_ << instruction->DebugName(); if (instruction->InputCount() > 0) { @@ -492,7 +486,8 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor { } else { StartAttributeStream("loop") << "B" << info->GetHeader()->GetBlockId(); } - } else if (IsReferenceTypePropagationPass() + } else if ((IsPass(ReferenceTypePropagation::kReferenceTypePropagationPassName) + || IsPass(HInliner::kInlinerPassName)) && (instruction->GetType() == Primitive::kPrimNot)) { ReferenceTypeInfo info = instruction->IsLoadClass() ? instruction->AsLoadClass()->GetLoadedClassRTI() diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 353881e47a..0363f203b2 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -148,7 +148,7 @@ static ArtMethod* FindVirtualOrInterfaceTarget(HInvoke* invoke, ArtMethod* resol // the target method. Since we check above the exact type of the receiver, // the only reason this can happen is an IncompatibleClassChangeError. return nullptr; - } else if (resolved_method->IsAbstract()) { + } else if (!resolved_method->IsInvokable()) { // The information we had on the receiver was not enough to find // the target method. Since we check above the exact type of the receiver, // the only reason this can happen is an IncompatibleClassChangeError. @@ -406,8 +406,8 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, &type_propagation, &sharpening, &simplify, - &dce, &fold, + &dce, }; for (size_t i = 0; i < arraysize(optimizations); ++i) { @@ -534,6 +534,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, ReferenceTypeInfo::Create(obj_handle, false /* is_exact */)); } + // Check the integrity of reference types and run another type propagation if needed. if ((return_replacement != nullptr) && (return_replacement->GetType() == Primitive::kPrimNot)) { if (!return_replacement->GetReferenceTypeInfo().IsValid()) { @@ -544,10 +545,20 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, DCHECK(return_replacement->IsPhi()); size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); ReferenceTypeInfo::TypeHandle return_handle = - handles_->NewHandle(resolved_method->GetReturnType(true /* resolve */, pointer_size)); + handles_->NewHandle(resolved_method->GetReturnType(true /* resolve */, pointer_size)); return_replacement->SetReferenceTypeInfo(ReferenceTypeInfo::Create( return_handle, return_handle->CannotBeAssignedFromOtherTypes() /* is_exact */)); } + + // If the return type is a refinement of the declared type run the type propagation again. + ReferenceTypeInfo return_rti = return_replacement->GetReferenceTypeInfo(); + ReferenceTypeInfo invoke_rti = invoke_instruction->GetReferenceTypeInfo(); + if (invoke_rti.IsStrictSupertypeOf(return_rti) + || (return_rti.IsExact() && !invoke_rti.IsExact()) + || !return_replacement->CanBeNull()) { + ReferenceTypePropagation rtp_fixup(graph_, handles_); + rtp_fixup.Run(); + } } return true; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 83f53d791b..73a44ee2cb 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -362,7 +362,11 @@ void HGraph::ComputeTryBlockInformation() { HBasicBlock* first_predecessor = block->GetPredecessors()[0]; DCHECK(!block->IsLoopHeader() || !block->GetLoopInformation()->IsBackEdge(*first_predecessor)); const HTryBoundary* try_entry = first_predecessor->ComputeTryEntryOfSuccessors(); - if (try_entry != nullptr) { + if (try_entry != nullptr && + (block->GetTryCatchInformation() == nullptr || + try_entry != &block->GetTryCatchInformation()->GetTryEntry())) { + // We are either setting try block membership for the first time or it + // has changed. block->SetTryCatchInformation(new (arena_) TryCatchInformation(*try_entry)); } } @@ -381,8 +385,9 @@ void HGraph::SimplifyCFG() { // Only split normal-flow edges. We cannot split exceptional edges as they // are synthesized (approximate real control flow), and we do not need to // anyway. Moves that would be inserted there are performed by the runtime. - for (size_t j = 0, e = block->NumberOfNormalSuccessors(); j < e; ++j) { - HBasicBlock* successor = block->GetSuccessors()[j]; + ArrayRef<HBasicBlock* const> normal_successors = block->GetNormalSuccessors(); + for (size_t j = 0, e = normal_successors.size(); j < e; ++j) { + HBasicBlock* successor = normal_successors[j]; DCHECK(!successor->IsCatchBlock()); if (successor == exit_block_) { // Throw->TryBoundary->Exit. Special case which we do not want to split @@ -391,7 +396,11 @@ void HGraph::SimplifyCFG() { DCHECK(block->GetSinglePredecessor()->GetLastInstruction()->IsThrow()); } else if (successor->GetPredecessors().size() > 1) { SplitCriticalEdge(block, successor); - --j; + // SplitCriticalEdge could have invalidated the `normal_successors` + // ArrayRef. We must re-acquire it. + normal_successors = block->GetNormalSuccessors(); + DCHECK_EQ(normal_successors[j]->GetSingleSuccessor(), successor); + DCHECK_EQ(e, normal_successors.size()); } } } @@ -1086,6 +1095,8 @@ HConstant* HBinaryOperation::TryStaticEvaluation() const { } else if (GetRight()->IsLongConstant()) { return Evaluate(GetLeft()->AsLongConstant(), GetRight()->AsLongConstant()); } + } else if (GetLeft()->IsNullConstant() && GetRight()->IsNullConstant()) { + return Evaluate(GetLeft()->AsNullConstant(), GetRight()->AsNullConstant()); } return nullptr; } @@ -1325,17 +1336,38 @@ bool HBasicBlock::HasSinglePhi() const { return !GetPhis().IsEmpty() && GetFirstPhi()->GetNext() == nullptr; } +ArrayRef<HBasicBlock* const> HBasicBlock::GetNormalSuccessors() const { + if (EndsWithTryBoundary()) { + // The normal-flow successor of HTryBoundary is always stored at index zero. + DCHECK_EQ(successors_[0], GetLastInstruction()->AsTryBoundary()->GetNormalFlowSuccessor()); + return ArrayRef<HBasicBlock* const>(successors_).SubArray(0u, 1u); + } else { + // All successors of blocks not ending with TryBoundary are normal. + return ArrayRef<HBasicBlock* const>(successors_); + } +} + +ArrayRef<HBasicBlock* const> HBasicBlock::GetExceptionalSuccessors() const { + if (EndsWithTryBoundary()) { + return GetLastInstruction()->AsTryBoundary()->GetExceptionHandlers(); + } else { + // Blocks not ending with TryBoundary do not have exceptional successors. + return ArrayRef<HBasicBlock* const>(); + } +} + bool HTryBoundary::HasSameExceptionHandlersAs(const HTryBoundary& other) const { - if (GetBlock()->GetSuccessors().size() != other.GetBlock()->GetSuccessors().size()) { + ArrayRef<HBasicBlock* const> handlers1 = GetExceptionHandlers(); + ArrayRef<HBasicBlock* const> handlers2 = other.GetExceptionHandlers(); + + size_t length = handlers1.size(); + if (length != handlers2.size()) { return false; } // Exception handlers need to be stored in the same order. - for (HExceptionHandlerIterator it1(*this), it2(other); - !it1.Done(); - it1.Advance(), it2.Advance()) { - DCHECK(!it2.Done()); - if (it1.Current() != it2.Current()) { + for (size_t i = 0; i < length; ++i) { + if (handlers1[i] != handlers2[i]) { return false; } } @@ -1388,7 +1420,7 @@ void HBasicBlock::DisconnectAndDelete() { // iteration. DCHECK(dominated_blocks_.empty()); - // Remove the block from all loops it is included in. + // (1) Remove the block from all loops it is included in. for (HLoopInformationOutwardIterator it(*this); !it.Done(); it.Advance()) { HLoopInformation* loop_info = it.Current(); loop_info->Remove(this); @@ -1400,17 +1432,34 @@ void HBasicBlock::DisconnectAndDelete() { } } - // Disconnect the block from its predecessors and update their control-flow - // instructions. + // (2) Disconnect the block from its predecessors and update their + // control-flow instructions. for (HBasicBlock* predecessor : predecessors_) { HInstruction* last_instruction = predecessor->GetLastInstruction(); + if (last_instruction->IsTryBoundary() && !IsCatchBlock()) { + // This block is the only normal-flow successor of the TryBoundary which + // makes `predecessor` dead. Since DCE removes blocks in post order, + // exception handlers of this TryBoundary were already visited and any + // remaining handlers therefore must be live. We remove `predecessor` from + // their list of predecessors. + DCHECK_EQ(last_instruction->AsTryBoundary()->GetNormalFlowSuccessor(), this); + while (predecessor->GetSuccessors().size() > 1) { + HBasicBlock* handler = predecessor->GetSuccessors()[1]; + DCHECK(handler->IsCatchBlock()); + predecessor->RemoveSuccessor(handler); + handler->RemovePredecessor(predecessor); + } + } + predecessor->RemoveSuccessor(this); uint32_t num_pred_successors = predecessor->GetSuccessors().size(); if (num_pred_successors == 1u) { // If we have one successor after removing one, then we must have - // had an HIf or HPackedSwitch, as they have more than one successor. - // Replace those with a HGoto. - DCHECK(last_instruction->IsIf() || last_instruction->IsPackedSwitch()); + // had an HIf, HPackedSwitch or HTryBoundary, as they have more than one + // successor. Replace those with a HGoto. + DCHECK(last_instruction->IsIf() || + last_instruction->IsPackedSwitch() || + (last_instruction->IsTryBoundary() && IsCatchBlock())); predecessor->RemoveInstruction(last_instruction); predecessor->AddInstruction(new (graph_->GetArena()) HGoto(last_instruction->GetDexPc())); } else if (num_pred_successors == 0u) { @@ -1419,15 +1468,17 @@ void HBasicBlock::DisconnectAndDelete() { // SSAChecker fails unless it is not removed during the pass too. predecessor->RemoveInstruction(last_instruction); } else { - // There are multiple successors left. This must come from a HPackedSwitch - // and we are in the middle of removing the HPackedSwitch. Like above, leave - // this alone, and the SSAChecker will fail if it is not removed as well. - DCHECK(last_instruction->IsPackedSwitch()); + // There are multiple successors left. The removed block might be a successor + // of a PackedSwitch which will be completely removed (perhaps replaced with + // a Goto), or we are deleting a catch block from a TryBoundary. In either + // case, leave `last_instruction` as is for now. + DCHECK(last_instruction->IsPackedSwitch() || + (last_instruction->IsTryBoundary() && IsCatchBlock())); } } predecessors_.clear(); - // Disconnect the block from its successors and update their phis. + // (3) Disconnect the block from its successors and update their phis. for (HBasicBlock* successor : successors_) { // Delete this block from the list of predecessors. size_t this_index = successor->GetPredecessorIndexOf(this); @@ -1437,30 +1488,57 @@ void HBasicBlock::DisconnectAndDelete() { // dominator of `successor` which violates the order DCHECKed at the top. DCHECK(!successor->predecessors_.empty()); - // Remove this block's entries in the successor's phis. - if (successor->predecessors_.size() == 1u) { - // The successor has just one predecessor left. Replace phis with the only - // remaining input. - for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { - HPhi* phi = phi_it.Current()->AsPhi(); - phi->ReplaceWith(phi->InputAt(1 - this_index)); - successor->RemovePhi(phi); - } - } else { - for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { - phi_it.Current()->AsPhi()->RemoveInputAt(this_index); + // Remove this block's entries in the successor's phis. Skip exceptional + // successors because catch phi inputs do not correspond to predecessor + // blocks but throwing instructions. Their inputs will be updated in step (4). + if (!successor->IsCatchBlock()) { + if (successor->predecessors_.size() == 1u) { + // The successor has just one predecessor left. Replace phis with the only + // remaining input. + for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + HPhi* phi = phi_it.Current()->AsPhi(); + phi->ReplaceWith(phi->InputAt(1 - this_index)); + successor->RemovePhi(phi); + } + } else { + for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + phi_it.Current()->AsPhi()->RemoveInputAt(this_index); + } } } } successors_.clear(); + // (4) Remove instructions and phis. Instructions should have no remaining uses + // except in catch phis. If an instruction is used by a catch phi at `index`, + // remove `index`-th input of all phis in the catch block since they are + // guaranteed dead. Note that we may miss dead inputs this way but the + // graph will always remain consistent. + for (HBackwardInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) { + HInstruction* insn = it.Current(); + while (insn->HasUses()) { + DCHECK(IsTryBlock()); + HUseListNode<HInstruction*>* use = insn->GetUses().GetFirst(); + size_t use_index = use->GetIndex(); + HBasicBlock* user_block = use->GetUser()->GetBlock(); + DCHECK(use->GetUser()->IsPhi() && user_block->IsCatchBlock()); + for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) { + phi_it.Current()->AsPhi()->RemoveInputAt(use_index); + } + } + + RemoveInstruction(insn); + } + for (HInstructionIterator it(GetPhis()); !it.Done(); it.Advance()) { + RemovePhi(it.Current()->AsPhi()); + } + // Disconnect from the dominator. dominator_->RemoveDominatedBlock(this); SetDominator(nullptr); - // Delete from the graph. The function safely deletes remaining instructions - // and updates the reverse post order. - graph_->DeleteDeadBlock(this); + // Delete from the graph, update reverse post order. + graph_->DeleteDeadEmptyBlock(this); SetGraph(nullptr); } @@ -1507,7 +1585,7 @@ void HBasicBlock::MergeWith(HBasicBlock* other) { other->predecessors_.clear(); // Delete `other` from the graph. The function updates reverse post order. - graph_->DeleteDeadBlock(other); + graph_->DeleteDeadEmptyBlock(other); other->SetGraph(nullptr); } @@ -1571,19 +1649,14 @@ static void MakeRoomFor(ArenaVector<HBasicBlock*>* blocks, std::copy_backward(blocks->begin() + after + 1u, blocks->begin() + old_size, blocks->end()); } -void HGraph::DeleteDeadBlock(HBasicBlock* block) { +void HGraph::DeleteDeadEmptyBlock(HBasicBlock* block) { DCHECK_EQ(block->GetGraph(), this); DCHECK(block->GetSuccessors().empty()); DCHECK(block->GetPredecessors().empty()); DCHECK(block->GetDominatedBlocks().empty()); DCHECK(block->GetDominator() == nullptr); - - for (HBackwardInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - block->RemoveInstruction(it.Current()); - } - for (HBackwardInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { - block->RemovePhi(it.Current()->AsPhi()); - } + DCHECK(block->GetInstructions().IsEmpty()); + DCHECK(block->GetPhis().IsEmpty()); if (block->IsExitBlock()) { exit_block_ = nullptr; @@ -1686,6 +1759,9 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // (2) the reverse post order of that graph, // (3) the potential loop information they are now in, // (4) try block membership. + // Note that we do not need to update catch phi inputs because they + // correspond to the register file of the outer method which the inlinee + // cannot modify. // We don't add the entry block, the exit block, and the first block, which // has been merged with `at`. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 4c4d0f268d..2878ac9899 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -35,6 +35,7 @@ #include "mirror/class.h" #include "offsets.h" #include "primitive.h" +#include "utils/array_ref.h" namespace art { @@ -240,8 +241,9 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { // put deoptimization instructions, etc. void TransformLoopHeaderForBCE(HBasicBlock* header); - // Removes `block` from the graph. - void DeleteDeadBlock(HBasicBlock* block); + // Removes `block` from the graph. Assumes `block` has been disconnected from + // other blocks and has no instructions or phis. + void DeleteDeadEmptyBlock(HBasicBlock* block); // Splits the edge between `block` and `successor` while preserving the // indices in the predecessor/successor lists. If there are multiple edges @@ -659,6 +661,9 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { return successors_; } + ArrayRef<HBasicBlock* const> GetNormalSuccessors() const; + ArrayRef<HBasicBlock* const> GetExceptionalSuccessors() const; + bool HasSuccessor(const HBasicBlock* block, size_t start_from = 0u) { return ContainsElement(successors_, block, start_from); } @@ -809,12 +814,6 @@ class HBasicBlock : public ArenaObject<kArenaAllocBasicBlock> { return GetPredecessorIndexOf(predecessor) == idx; } - // Returns the number of non-exceptional successors. SsaChecker ensures that - // these are stored at the beginning of the successor list. - size_t NumberOfNormalSuccessors() const { - return EndsWithTryBoundary() ? 1 : GetSuccessors().size(); - } - // Create a new block between this block and its predecessors. The new block // is added to the graph, all predecessor edges are relinked to it and an edge // is created to `this`. Returns the new empty block. Reverse post order or @@ -1732,6 +1731,13 @@ class ReferenceTypeInfo : ValueObject { return GetTypeHandle()->IsAssignableFrom(rti.GetTypeHandle().Get()); } + bool IsStrictSupertypeOf(ReferenceTypeInfo rti) const SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK(IsValid()); + DCHECK(rti.IsValid()); + return GetTypeHandle().Get() != rti.GetTypeHandle().Get() && + GetTypeHandle()->IsAssignableFrom(rti.GetTypeHandle().Get()); + } + // Returns true if the type information provide the same amount of details. // Note that it does not mean that the instructions have the same actual type // (because the type can be the result of a merge). @@ -2397,6 +2403,10 @@ class HTryBoundary : public HTemplateInstruction<0> { // Returns the block's non-exceptional successor (index zero). HBasicBlock* GetNormalFlowSuccessor() const { return GetBlock()->GetSuccessors()[0]; } + ArrayRef<HBasicBlock* const> GetExceptionHandlers() const { + return ArrayRef<HBasicBlock* const>(GetBlock()->GetSuccessors()).SubArray(1u); + } + // Returns whether `handler` is among its exception handlers (non-zero index // successors). bool HasExceptionHandler(const HBasicBlock& handler) const { @@ -2424,25 +2434,6 @@ class HTryBoundary : public HTemplateInstruction<0> { DISALLOW_COPY_AND_ASSIGN(HTryBoundary); }; -// Iterator over exception handlers of a given HTryBoundary, i.e. over -// exceptional successors of its basic block. -class HExceptionHandlerIterator : public ValueObject { - public: - explicit HExceptionHandlerIterator(const HTryBoundary& try_boundary) - : block_(*try_boundary.GetBlock()), index_(block_.NumberOfNormalSuccessors()) {} - - bool Done() const { return index_ == block_.GetSuccessors().size(); } - HBasicBlock* Current() const { return block_.GetSuccessors()[index_]; } - size_t CurrentSuccessorIndex() const { return index_; } - void Advance() { ++index_; } - - private: - const HBasicBlock& block_; - size_t index_; - - DISALLOW_COPY_AND_ASSIGN(HExceptionHandlerIterator); -}; - // Deoptimize to interpreter, upon checking a condition. class HDeoptimize : public HTemplateInstruction<1> { public: @@ -2611,6 +2602,11 @@ class HBinaryOperation : public HExpression<2> { VLOG(compiler) << DebugName() << " is not defined for the (long, int) case."; return nullptr; } + virtual HConstant* Evaluate(HNullConstant* x ATTRIBUTE_UNUSED, + HNullConstant* y ATTRIBUTE_UNUSED) const { + VLOG(compiler) << DebugName() << " is not defined for the (null, null) case."; + return nullptr; + } // Returns an input that can legally be used as the right input and is // constant, or null. @@ -2701,6 +2697,10 @@ class HEqual : public HCondition { return GetBlock()->GetGraph()->GetIntConstant( Compute(x->GetValue(), y->GetValue()), GetDexPc()); } + HConstant* Evaluate(HNullConstant* x ATTRIBUTE_UNUSED, + HNullConstant* y ATTRIBUTE_UNUSED) const OVERRIDE { + return GetBlock()->GetGraph()->GetIntConstant(1); + } DECLARE_INSTRUCTION(Equal); @@ -2733,6 +2733,10 @@ class HNotEqual : public HCondition { return GetBlock()->GetGraph()->GetIntConstant( Compute(x->GetValue(), y->GetValue()), GetDexPc()); } + HConstant* Evaluate(HNullConstant* x ATTRIBUTE_UNUSED, + HNullConstant* y ATTRIBUTE_UNUSED) const OVERRIDE { + return GetBlock()->GetGraph()->GetIntConstant(0); + } DECLARE_INSTRUCTION(NotEqual); diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index 6e85a82849..2be0680561 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -405,20 +405,9 @@ static void MaybeRunInliner(HGraph* graph, if (!should_inline) { return; } - - ArenaAllocator* arena = graph->GetArena(); - HInliner* inliner = new (arena) HInliner( + HInliner* inliner = new (graph->GetArena()) HInliner( graph, codegen, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); - ReferenceTypePropagation* type_propagation = - new (arena) ReferenceTypePropagation(graph, handles, - "reference_type_propagation_after_inlining"); - - HOptimization* optimizations[] = { - inliner, - // Run another type propagation phase: inlining will open up more opportunities - // to remove checkcast/instanceof and null checks. - type_propagation, - }; + HOptimization* optimizations[] = { inliner }; RunOptimizations(optimizations, arraysize(optimizations), pass_observer); } @@ -530,6 +519,7 @@ static void RunOptimizations(HGraph* graph, // pipeline for all methods. if (graph->HasTryCatch()) { HOptimization* optimizations2[] = { + boolean_simplify, side_effects, gvn, dce2, diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index 659da068a9..ecc085b985 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -99,17 +99,9 @@ ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph, } } -void ReferenceTypePropagation::Run() { - // To properly propagate type info we need to visit in the dominator-based order. - // Reverse post order guarantees a node's dominators are visited first. - // We take advantage of this order in `VisitBasicBlock`. - for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { - VisitBasicBlock(it.Current()); - } - ProcessWorklist(); - +void ReferenceTypePropagation::ValidateTypes() { + // TODO: move this to the graph checker. if (kIsDebugBuild) { - // TODO: move this to the graph checker. ScopedObjectAccess soa(Thread::Current()); for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { HBasicBlock* block = it.Current(); @@ -135,6 +127,18 @@ void ReferenceTypePropagation::Run() { } } +void ReferenceTypePropagation::Run() { + // To properly propagate type info we need to visit in the dominator-based order. + // Reverse post order guarantees a node's dominators are visited first. + // We take advantage of this order in `VisitBasicBlock`. + for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) { + VisitBasicBlock(it.Current()); + } + + ProcessWorklist(); + ValidateTypes(); +} + void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) { RTPVisitor visitor(graph_, handles_, diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h index 5493601adc..5c05592726 100644 --- a/compiler/optimizing/reference_type_propagation.h +++ b/compiler/optimizing/reference_type_propagation.h @@ -56,6 +56,8 @@ class ReferenceTypePropagation : public HOptimization { ReferenceTypeInfo MergeTypes(const ReferenceTypeInfo& a, const ReferenceTypeInfo& b) SHARED_REQUIRES(Locks::mutator_lock_); + void ValidateTypes(); + StackHandleScopeCollection* handles_; ArenaVector<HInstruction*> worklist_; diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index ef22c816a0..d399bc2d7a 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -1525,7 +1525,7 @@ void RegisterAllocator::InsertParallelMoveAtExitOf(HBasicBlock* block, DCHECK(IsValidDestination(destination)) << destination; if (source.Equals(destination)) return; - DCHECK_EQ(block->NumberOfNormalSuccessors(), 1u); + DCHECK_EQ(block->GetNormalSuccessors().size(), 1u); HInstruction* last = block->GetLastInstruction(); // We insert moves at exit for phi predecessors and connecting blocks. // A block ending with an if or a packed switch cannot branch to a block @@ -1752,7 +1752,7 @@ void RegisterAllocator::ConnectSplitSiblings(LiveInterval* interval, // If `from` has only one successor, we can put the moves at the exit of it. Otherwise // we need to put the moves at the entry of `to`. - if (from->NumberOfNormalSuccessors() == 1) { + if (from->GetNormalSuccessors().size() == 1) { InsertParallelMoveAtExitOf(from, interval->GetParent()->GetDefinedBy(), source->ToLocation(), @@ -1894,7 +1894,7 @@ void RegisterAllocator::Resolve() { HInstruction* phi = inst_it.Current(); for (size_t i = 0, e = current->GetPredecessors().size(); i < e; ++i) { HBasicBlock* predecessor = current->GetPredecessors()[i]; - DCHECK_EQ(predecessor->NumberOfNormalSuccessors(), 1u); + DCHECK_EQ(predecessor->GetNormalSuccessors().size(), 1u); HInstruction* input = phi->InputAt(i); Location source = input->GetLiveInterval()->GetLocationAt( predecessor->GetLifetimeEnd() - 1); diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index 4565590bc3..5190eb3b26 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -660,8 +660,7 @@ void SsaBuilder::VisitInstruction(HInstruction* instruction) { if (instruction->CanThrowIntoCatchBlock()) { const HTryBoundary& try_entry = instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry(); - for (HExceptionHandlerIterator it(try_entry); !it.Done(); it.Advance()) { - HBasicBlock* catch_block = it.Current(); + for (HBasicBlock* catch_block : try_entry.GetExceptionHandlers()) { ArenaVector<HInstruction*>* handler_locals = GetLocalsFor(catch_block); DCHECK_EQ(handler_locals->size(), current_locals_->size()); for (size_t vreg = 0, e = current_locals_->size(); vreg < e; ++vreg) { diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 92ed58cb86..68cf6d9233 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1357,7 +1357,7 @@ class Dex2Oat FINAL { int32_t image_patch_delta = 0; if (app_image_ && image_base_ == 0) { - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); image_base_ = RoundUp( reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()), kPageSize); @@ -1370,7 +1370,7 @@ class Dex2Oat FINAL { if (!IsBootImage()) { TimingLogger::ScopedTiming t3("Loading image checksum", timings_); - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); image_file_location_oat_checksum = image_space->GetImageHeader().GetOatChecksum(); image_file_location_oat_data_begin = reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatDataBegin()); diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index 304d4e5860..5e710530b9 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -814,7 +814,7 @@ class ImgDiagDumper { static const ImageHeader& GetBootImageHeader() { gc::Heap* heap = Runtime::Current()->GetHeap(); - gc::space::ImageSpace* image_space = heap->GetImageSpace(); + gc::space::ImageSpace* image_space = heap->GetBootImageSpace(); CHECK(image_space != nullptr); const ImageHeader& image_header = image_space->GetImageHeader(); return image_header; @@ -838,7 +838,7 @@ static int DumpImage(Runtime* runtime, const char* image_location, std::ostream* os, pid_t image_diff_pid) { ScopedObjectAccess soa(Thread::Current()); gc::Heap* heap = runtime->GetHeap(); - gc::space::ImageSpace* image_space = heap->GetImageSpace(); + gc::space::ImageSpace* image_space = heap->GetBootImageSpace(); CHECK(image_space != nullptr); const ImageHeader& image_header = image_space->GetImageHeader(); if (!image_header.IsValid()) { diff --git a/imgdiag/imgdiag_test.cc b/imgdiag/imgdiag_test.cc index 82bc8b9521..0d6a8c9dee 100644 --- a/imgdiag/imgdiag_test.cc +++ b/imgdiag/imgdiag_test.cc @@ -42,7 +42,7 @@ class ImgDiagTest : public CommonRuntimeTest { CommonRuntimeTest::SetUp(); // We loaded the runtime with an explicit image. Therefore the image space must exist. - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); ASSERT_TRUE(image_space != nullptr); boot_image_location_ = image_space->GetImageLocation(); } diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index a163380b25..5a060af85c 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -2374,7 +2374,7 @@ static int DumpImage(Runtime* runtime, const char* image_location, OatDumperOpti ScopedObjectAccess soa(Thread::Current()); gc::Heap* heap = runtime->GetHeap(); - gc::space::ImageSpace* image_space = heap->GetImageSpace(); + gc::space::ImageSpace* image_space = heap->GetBootImageSpace(); CHECK(image_space != nullptr); const ImageHeader& image_header = image_space->GetImageHeader(); if (!image_header.IsValid()) { diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index 88622ccc9b..c587f68234 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -177,18 +177,21 @@ bool PatchOat::Patch(const std::string& image_location, off_t delta, t.NewTiming("Image and oat Patching setup"); // Create the map where we will write the image patches to. std::string error_msg; - std::unique_ptr<MemMap> image(MemMap::MapFile(image_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, - input_image->Fd(), 0, + std::unique_ptr<MemMap> image(MemMap::MapFile(image_len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + input_image->Fd(), + 0, + /*low_4gb*/false, input_image->GetPath().c_str(), &error_msg)); if (image.get() == nullptr) { LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg; return false; } - gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace(); - PatchOat p(isa, image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), - delta, timings); + PatchOat p(isa, image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), delta, timings); t.NewTiming("Patching files"); if (!p.PatchImage()) { LOG(ERROR) << "Failed to patch image file " << input_image->GetPath(); @@ -273,15 +276,19 @@ bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t d t.NewTiming("Image and oat Patching setup"); // Create the map where we will write the image patches to. std::string error_msg; - std::unique_ptr<MemMap> image(MemMap::MapFile(image_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, - input_image->Fd(), 0, + std::unique_ptr<MemMap> image(MemMap::MapFile(image_len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + input_image->Fd(), + 0, + /*low_4gb*/false, input_image->GetPath().c_str(), &error_msg)); if (image.get() == nullptr) { LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg; return false; } - gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetBootImageSpace(); std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat, PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg)); diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 95f0ccb419..5fd8969248 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -809,7 +809,90 @@ END_MACRO // Generate the allocation entrypoints for each allocator. GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR -GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_rosalloc, RosAlloc) +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_rosalloc, RosAlloc). +DEFINE_FUNCTION art_quick_alloc_object_rosalloc + // Fast path rosalloc allocation. + // RDI: type_idx, RSI: ArtMethod*, RAX: return value + // RDX, RCX, R8, R9: free. + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array + // Load the class (edx) + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx + testl %edx, %edx // Check null class + jz .Lart_quick_alloc_object_rosalloc_slow_path + // Check class status. + cmpl LITERAL(MIRROR_CLASS_STATUS_INITIALIZED), MIRROR_CLASS_STATUS_OFFSET(%rdx) + jne .Lart_quick_alloc_object_rosalloc_slow_path + // We don't need a fence (between the + // the status and the access flag + // loads) here because every load is + // a load acquire on x86. + // Check access flags has + // kAccClassIsFinalizable + testl LITERAL(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), MIRROR_CLASS_ACCESS_FLAGS_OFFSET(%rdx) + jnz .Lart_quick_alloc_object_rosalloc_slow_path + // Check if the thread local + // allocation stack has room. + movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread + movq THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET(%r8), %rcx // rcx = alloc stack top. + cmpq THREAD_LOCAL_ALLOC_STACK_END_OFFSET(%r8), %rcx + jae .Lart_quick_alloc_object_rosalloc_slow_path + // Load the object size + movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%rdx), %eax + // Check if the size is for a thread + // local allocation + cmpl LITERAL(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), %eax + ja .Lart_quick_alloc_object_rosalloc_slow_path + // Compute the rosalloc bracket index + // from the size. + // Align up the size by the rosalloc + // bracket quantum size and divide + // by the quantum size and subtract + // by 1. This code is a shorter but + // equivalent version. + subq LITERAL(1), %rax + shrq LITERAL(ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT), %rax + // Load the rosalloc run (r9) + movq THREAD_ROSALLOC_RUNS_OFFSET(%r8, %rax, __SIZEOF_POINTER__), %r9 + // Load the free list head (rax). This + // will be the return val. + movq (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)(%r9), %rax + testq %rax, %rax + jz .Lart_quick_alloc_object_rosalloc_slow_path + // "Point of no slow path". Won't go to the slow path from here on. OK to clobber rdi and rsi. + // Push the new object onto the thread + // local allocation stack and + // increment the thread local + // allocation stack top. + movl %eax, (%rcx) + addq LITERAL(COMPRESSED_REFERENCE_SIZE), %rcx + movq %rcx, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET(%r8) + // Load the next pointer of the head + // and update the list head with the + // next pointer. + movq ROSALLOC_SLOT_NEXT_OFFSET(%rax), %rcx + movq %rcx, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)(%r9) + // Store the class pointer in the + // header. This also overwrites the + // next pointer. The offsets are + // asserted to match. +#if ROSALLOC_SLOT_NEXT_OFFSET != MIRROR_OBJECT_CLASS_OFFSET +#error "Class pointer needs to overwrite next pointer." +#endif + POISON_HEAP_REF edx + movl %edx, MIRROR_OBJECT_CLASS_OFFSET(%rax) + // Decrement the size of the free list + decl (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)(%r9) + // No fence necessary for x86. + ret +.Lart_quick_alloc_object_rosalloc_slow_path: + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save ref containing registers for GC + // Outgoing argument set up + movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() + call SYMBOL(artAllocObjectFromCodeRosAlloc) // cxx_name(arg0, arg1, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME // restore frame up to return address + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception +END_FUNCTION art_quick_alloc_object_rosalloc + // A handle-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). DEFINE_FUNCTION art_quick_alloc_object_tlab // Fast path tlab allocation. diff --git a/runtime/art_method.cc b/runtime/art_method.cc index dbb546da29..f7ed81254f 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -67,6 +67,18 @@ mirror::String* ArtMethod::GetNameAsString(Thread* self) { dex_cache); } +void ArtMethod::ThrowInvocationTimeError() { + DCHECK(!IsInvokable()); + // NOTE: IsDefaultConflicting must be first since the actual method might or might not be abstract + // due to the way we select it. + if (IsDefaultConflicting()) { + ThrowIncompatibleClassChangeErrorForMethodConflict(this); + } else { + DCHECK(IsAbstract()); + ThrowAbstractMethodError(this); + } +} + InvokeType ArtMethod::GetInvokeType() { // TODO: kSuper? if (GetDeclaringClass()->IsInterface()) { @@ -330,6 +342,10 @@ void ArtMethod::UnregisterNative() { RegisterNative(GetJniDlsymLookupStub(), false); } +bool ArtMethod::IsOverridableByDefaultMethod() { + return GetDeclaringClass()->IsInterface(); +} + bool ArtMethod::EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> params) { auto* dex_cache = GetDexCache(); auto* dex_file = dex_cache->GetDexFile(); diff --git a/runtime/art_method.h b/runtime/art_method.h index 201b3e64da..5a2d6c36ed 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -136,6 +136,19 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccMiranda) != 0; } + // Returns true if invoking this method will not throw an AbstractMethodError or + // IncompatibleClassChangeError. + bool IsInvokable() { + return !IsAbstract() && !IsDefaultConflicting(); + } + + // A default conflict method is a special sentinel method that stands for a conflict between + // multiple default methods. It cannot be invoked, throwing an IncompatibleClassChangeError if one + // attempts to do so. + bool IsDefaultConflicting() { + return (GetAccessFlags() & kAccDefaultConflict) != 0u; + } + // This is set by the class linker. bool IsDefault() { return (GetAccessFlags() & kAccDefault) != 0; @@ -170,12 +183,14 @@ class ArtMethod FINAL { } // Returns true if this method could be overridden by a default method. - bool IsOverridableByDefaultMethod() { - return IsDefault() || IsAbstract(); - } + bool IsOverridableByDefaultMethod() SHARED_REQUIRES(Locks::mutator_lock_); bool CheckIncompatibleClassChange(InvokeType type) SHARED_REQUIRES(Locks::mutator_lock_); + // Throws the error that would result from trying to invoke this method (i.e. + // IncompatibleClassChangeError or AbstractMethodError). Only call if !IsInvokable(); + void ThrowInvocationTimeError() SHARED_REQUIRES(Locks::mutator_lock_); + uint16_t GetMethodIndex() SHARED_REQUIRES(Locks::mutator_lock_); // Doesn't do erroneous / unresolved class checks. diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc index 71afa0f709..771b2d0509 100644 --- a/runtime/base/arena_allocator.cc +++ b/runtime/base/arena_allocator.cc @@ -316,22 +316,22 @@ void ArenaAllocator::UpdateBytesAllocated() { } void* ArenaAllocator::AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind) { + // We mark all memory for a newly retrieved arena as inaccessible and then + // mark only the actually allocated memory as defined. That leaves red zones + // and padding between allocations marked as inaccessible. size_t rounded_bytes = RoundUp(bytes + kMemoryToolRedZoneBytes, 8); if (UNLIKELY(ptr_ + rounded_bytes > end_)) { // Obtain a new block. ObtainNewArenaForAllocation(rounded_bytes); CHECK(ptr_ != nullptr); - MEMORY_TOOL_MAKE_UNDEFINED(ptr_, end_ - ptr_); + MEMORY_TOOL_MAKE_NOACCESS(ptr_, end_ - ptr_); } ArenaAllocatorStats::RecordAlloc(rounded_bytes, kind); uint8_t* ret = ptr_; ptr_ += rounded_bytes; - // Check that the memory is already zeroed out. - for (uint8_t* ptr = ret; ptr < ptr_; ++ptr) { - CHECK_EQ(*ptr, 0U); - } MEMORY_TOOL_MAKE_DEFINED(ret, bytes); - MEMORY_TOOL_MAKE_NOACCESS(ret + bytes, rounded_bytes - bytes); + // Check that the memory is already zeroed out. + DCHECK(std::all_of(ret, ret + bytes, [](uint8_t val) { return val == 0u; })); return ret; } diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h index ace6c388af..36334c4129 100644 --- a/runtime/base/arena_allocator.h +++ b/runtime/base/arena_allocator.h @@ -288,6 +288,11 @@ class ArenaPool { DISALLOW_COPY_AND_ASSIGN(ArenaPool); }; +// Fast single-threaded allocator for zero-initialized memory chunks. +// +// Memory is allocated from ArenaPool in large chunks and then rationed through +// the ArenaAllocator. It's returned to the ArenaPool only when the ArenaAllocator +// is destroyed. class ArenaAllocator : private DebugStackRefCounter, private ArenaAllocatorStats, private ArenaAllocatorMemoryTool { public: diff --git a/runtime/base/scoped_arena_allocator.cc b/runtime/base/scoped_arena_allocator.cc index 31f96e4783..90c6ee34ec 100644 --- a/runtime/base/scoped_arena_allocator.cc +++ b/runtime/base/scoped_arena_allocator.cc @@ -91,16 +91,19 @@ void ArenaStack::UpdateBytesAllocated() { } void* ArenaStack::AllocWithMemoryTool(size_t bytes, ArenaAllocKind kind) { + // We mark all memory for a newly retrieved arena as inaccessible and then + // mark only the actually allocated memory as defined. That leaves red zones + // and padding between allocations marked as inaccessible. size_t rounded_bytes = RoundUp(bytes + kMemoryToolRedZoneBytes, 8); uint8_t* ptr = top_ptr_; if (UNLIKELY(static_cast<size_t>(top_end_ - ptr) < rounded_bytes)) { ptr = AllocateFromNextArena(rounded_bytes); CHECK(ptr != nullptr) << "Failed to allocate memory"; + MEMORY_TOOL_MAKE_NOACCESS(ptr, top_end_); } CurrentStats()->RecordAlloc(bytes, kind); top_ptr_ = ptr + rounded_bytes; MEMORY_TOOL_MAKE_UNDEFINED(ptr, bytes); - MEMORY_TOOL_MAKE_NOACCESS(ptr + bytes, rounded_bytes - bytes); return ptr; } diff --git a/runtime/base/scoped_arena_allocator.h b/runtime/base/scoped_arena_allocator.h index a30c73d749..a87153bd77 100644 --- a/runtime/base/scoped_arena_allocator.h +++ b/runtime/base/scoped_arena_allocator.h @@ -42,6 +42,7 @@ enum class ArenaFreeTag : uint8_t { static constexpr size_t kArenaAlignment = 8; // Holds a list of Arenas for use by ScopedArenaAllocator stack. +// The memory is returned to the ArenaPool when the ArenaStack is destroyed. class ArenaStack : private DebugStackRefCounter, private ArenaAllocatorMemoryTool { public: explicit ArenaStack(ArenaPool* arena_pool); @@ -121,6 +122,12 @@ class ArenaStack : private DebugStackRefCounter, private ArenaAllocatorMemoryToo DISALLOW_COPY_AND_ASSIGN(ArenaStack); }; +// Fast single-threaded allocator. Allocated chunks are _not_ guaranteed to be zero-initialized. +// +// Unlike the ArenaAllocator, ScopedArenaAllocator is intended for relatively short-lived +// objects and allows nesting multiple allocators. Only the top allocator can be used but +// once it's destroyed, its memory can be reused by the next ScopedArenaAllocator on the +// stack. This is facilitated by returning the memory to the ArenaStack. class ScopedArenaAllocator : private DebugStackReference, private DebugStackRefCounter, private ArenaAllocatorStats { public: diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 5dac95d3b3..f649972096 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -303,7 +303,7 @@ static void ShuffleForward(size_t* current_field_idx, ClassLinker::ClassLinker(InternTable* intern_table) // dex_lock_ is recursive as it may be used in stack dumping. : dex_lock_("ClassLinker dex lock", kDefaultMutexLevel), - dex_cache_image_class_lookup_required_(false), + dex_cache_boot_image_class_lookup_required_(false), failed_dex_cache_class_lookups_(0), class_roots_(nullptr), array_iftable_(nullptr), @@ -794,7 +794,7 @@ static void SanityCheckObjectsCallback(mirror::Object* obj, void* arg ATTRIBUTE_ CHECK_EQ(field.GetDeclaringClass(), klass); } auto* runtime = Runtime::Current(); - auto* image_space = runtime->GetHeap()->GetImageSpace(); + auto* image_space = runtime->GetHeap()->GetBootImageSpace(); auto pointer_size = runtime->GetClassLinker()->GetImagePointerSize(); for (auto& m : klass->GetDirectMethods(pointer_size)) { SanityCheckArtMethod(&m, klass, image_space); @@ -855,10 +855,10 @@ void ClassLinker::InitFromImage() { Runtime* const runtime = Runtime::Current(); Thread* const self = Thread::Current(); gc::Heap* const heap = runtime->GetHeap(); - gc::space::ImageSpace* const space = heap->GetImageSpace(); + gc::space::ImageSpace* const space = heap->GetBootImageSpace(); CHECK(space != nullptr); image_pointer_size_ = space->GetImageHeader().GetPointerSize(); - dex_cache_image_class_lookup_required_ = true; + dex_cache_boot_image_class_lookup_required_ = true; const OatFile* oat_file = runtime->GetOatFileManager().RegisterImageOatFile(space); DCHECK(oat_file != nullptr); CHECK_EQ(oat_file->GetOatHeader().GetImageFileLocationOatChecksum(), 0U); @@ -1086,8 +1086,8 @@ void ClassLinker::VisitClassesInternal(ClassVisitor* visitor) { } void ClassLinker::VisitClasses(ClassVisitor* visitor) { - if (dex_cache_image_class_lookup_required_) { - MoveImageClassesToClassTable(); + if (dex_cache_boot_image_class_lookup_required_) { + AddBootImageClassesToClassTable(); } Thread* const self = Thread::Current(); ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_); @@ -1858,7 +1858,7 @@ const OatFile::OatMethod ClassLinker::FindOatMethodFor(ArtMethod* method, bool* // Special case to get oat code without overwriting a trampoline. const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { - CHECK(!method->IsAbstract()) << PrettyMethod(method); + CHECK(method->IsInvokable()) << PrettyMethod(method); if (method->IsProxyMethod()) { return GetQuickProxyInvokeHandler(); } @@ -1878,7 +1878,7 @@ const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { } const void* ClassLinker::GetOatMethodQuickCodeFor(ArtMethod* method) { - if (method->IsNative() || method->IsAbstract() || method->IsProxyMethod()) { + if (method->IsNative() || !method->IsInvokable() || method->IsProxyMethod()) { return nullptr; } bool found; @@ -1973,6 +1973,13 @@ void ClassLinker::FixupStaticTrampolines(mirror::Class* klass) { // Ignore virtual methods on the iterator. } +void ClassLinker::EnsureThrowsInvocationError(ArtMethod* method) { + DCHECK(method != nullptr); + DCHECK(!method->IsInvokable()); + method->SetEntryPointFromQuickCompiledCodePtrSize(quick_to_interpreter_bridge_trampoline_, + image_pointer_size_); +} + void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class, uint32_t class_def_method_index) { Runtime* const runtime = Runtime::Current(); @@ -1992,8 +1999,8 @@ void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class // Install entry point from interpreter. bool enter_interpreter = NeedsInterpreter(method, method->GetEntryPointFromQuickCompiledCode()); - if (method->IsAbstract()) { - method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); + if (!method->IsInvokable()) { + EnsureThrowsInvocationError(method); return; } @@ -2636,11 +2643,13 @@ mirror::Class* ClassLinker::InsertClass(const char* descriptor, mirror::Class* k if (existing != nullptr) { return existing; } - if (kIsDebugBuild && !klass->IsTemp() && class_loader == nullptr && - dex_cache_image_class_lookup_required_) { + if (kIsDebugBuild && + !klass->IsTemp() && + class_loader == nullptr && + dex_cache_boot_image_class_lookup_required_) { // Check a class loaded with the system class loader matches one in the image if the class // is in the image. - existing = LookupClassFromImage(descriptor); + existing = LookupClassFromBootImage(descriptor); if (existing != nullptr) { CHECK_EQ(klass, existing); } @@ -2684,11 +2693,11 @@ mirror::Class* ClassLinker::LookupClass(Thread* self, } } } - if (class_loader != nullptr || !dex_cache_image_class_lookup_required_) { + if (class_loader != nullptr || !dex_cache_boot_image_class_lookup_required_) { return nullptr; } // Lookup failed but need to search dex_caches_. - mirror::Class* result = LookupClassFromImage(descriptor); + mirror::Class* result = LookupClassFromBootImage(descriptor); if (result != nullptr) { result = InsertClass(descriptor, result, hash); } else { @@ -2697,37 +2706,43 @@ mirror::Class* ClassLinker::LookupClass(Thread* self, // classes into the class table. constexpr uint32_t kMaxFailedDexCacheLookups = 1000; if (++failed_dex_cache_class_lookups_ > kMaxFailedDexCacheLookups) { - MoveImageClassesToClassTable(); + AddBootImageClassesToClassTable(); } } return result; } -static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches() +static mirror::ObjectArray<mirror::DexCache>* GetImageDexCaches(gc::space::ImageSpace* image_space) SHARED_REQUIRES(Locks::mutator_lock_) { - gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetImageSpace(); - CHECK(image != nullptr); - mirror::Object* root = image->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches); + CHECK(image_space != nullptr); + mirror::Object* root = image_space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches); + DCHECK(root != nullptr); return root->AsObjectArray<mirror::DexCache>(); } -void ClassLinker::MoveImageClassesToClassTable() { +void ClassLinker::AddBootImageClassesToClassTable() { + if (dex_cache_boot_image_class_lookup_required_) { + AddImageClassesToClassTable(Runtime::Current()->GetHeap()->GetBootImageSpace(), + /*class_loader*/nullptr); + dex_cache_boot_image_class_lookup_required_ = false; + } +} + +void ClassLinker::AddImageClassesToClassTable(gc::space::ImageSpace* image_space, + mirror::ClassLoader* class_loader) { Thread* self = Thread::Current(); WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); - if (!dex_cache_image_class_lookup_required_) { - return; // All dex cache classes are already in the class table. - } ScopedAssertNoThreadSuspension ants(self, "Moving image classes to class table"); - mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(); + mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(image_space); std::string temp; - ClassTable* const class_table = InsertClassTableForClassLoader(nullptr); + ClassTable* const class_table = InsertClassTableForClassLoader(class_loader); for (int32_t i = 0; i < dex_caches->GetLength(); i++) { mirror::DexCache* dex_cache = dex_caches->Get(i); GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes(); for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) { mirror::Class* klass = types[j].Read(); if (klass != nullptr) { - DCHECK(klass->GetClassLoader() == nullptr); + DCHECK_EQ(klass->GetClassLoader(), class_loader); const char* descriptor = klass->GetDescriptor(&temp); size_t hash = ComputeModifiedUtf8Hash(descriptor); mirror::Class* existing = class_table->Lookup(descriptor, hash); @@ -2743,7 +2758,6 @@ void ClassLinker::MoveImageClassesToClassTable() { } } } - dex_cache_image_class_lookup_required_ = false; } class MoveClassTableToPreZygoteVisitor : public ClassLoaderVisitor { @@ -2767,9 +2781,10 @@ void ClassLinker::MoveClassTableToPreZygote() { VisitClassLoaders(&visitor); } -mirror::Class* ClassLinker::LookupClassFromImage(const char* descriptor) { +mirror::Class* ClassLinker::LookupClassFromBootImage(const char* descriptor) { ScopedAssertNoThreadSuspension ants(Thread::Current(), "Image class lookup"); - mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches(); + mirror::ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches( + Runtime::Current()->GetHeap()->GetBootImageSpace()); for (int32_t i = 0; i < dex_caches->GetLength(); ++i) { mirror::DexCache* dex_cache = dex_caches->Get(i); const DexFile* dex_file = dex_cache->GetDexFile(); @@ -2811,8 +2826,8 @@ class LookupClassesVisitor : public ClassLoaderVisitor { void ClassLinker::LookupClasses(const char* descriptor, std::vector<mirror::Class*>& result) { result.clear(); - if (dex_cache_image_class_lookup_required_) { - MoveImageClassesToClassTable(); + if (dex_cache_boot_image_class_lookup_required_) { + AddBootImageClassesToClassTable(); } Thread* const self = Thread::Current(); ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_); @@ -2825,6 +2840,48 @@ void ClassLinker::LookupClasses(const char* descriptor, std::vector<mirror::Clas VisitClassLoaders(&visitor); } +bool ClassLinker::AttemptSupertypeVerification(Thread* self, + Handle<mirror::Class> klass, + Handle<mirror::Class> supertype) { + DCHECK(self != nullptr); + DCHECK(klass.Get() != nullptr); + DCHECK(supertype.Get() != nullptr); + + StackHandleScope<1> hs(self); + // Acquire lock to prevent races on verifying the super class. + ObjectLock<mirror::Class> super_lock(self, supertype); + + if (!supertype->IsVerified() && !supertype->IsErroneous()) { + VerifyClass(self, supertype); + } + if (supertype->IsCompileTimeVerified()) { + // Either we are verified or we soft failed and need to retry at runtime. + return true; + } + // If we got this far then we have a hard failure. + std::string error_msg = + StringPrintf("Rejecting class %s that attempts to sub-type erroneous class %s", + PrettyDescriptor(klass.Get()).c_str(), + PrettyDescriptor(supertype.Get()).c_str()); + LOG(WARNING) << error_msg << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); + Handle<mirror::Throwable> cause(hs.NewHandle(self->GetException())); + if (cause.Get() != nullptr) { + // Set during VerifyClass call (if at all). + self->ClearException(); + } + // Change into a verify error. + ThrowVerifyError(klass.Get(), "%s", error_msg.c_str()); + if (cause.Get() != nullptr) { + self->GetException()->SetCause(cause.Get()); + } + ClassReference ref(klass->GetDexCache()->GetDexFile(), klass->GetDexClassDefIndex()); + if (Runtime::Current()->IsAotCompiler()) { + Runtime::Current()->GetCompilerCallbacks()->ClassRejected(ref); + } + mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + return false; +} + void ClassLinker::VerifyClass(Thread* self, Handle<mirror::Class> klass) { // TODO: assert that the monitor on the Class is held ObjectLock<mirror::Class> lock(self, klass); @@ -2875,56 +2932,70 @@ void ClassLinker::VerifyClass(Thread* self, Handle<mirror::Class> klass) { // Verify super class. StackHandleScope<2> hs(self); - Handle<mirror::Class> super(hs.NewHandle(klass->GetSuperClass())); - if (super.Get() != nullptr) { - // Acquire lock to prevent races on verifying the super class. - ObjectLock<mirror::Class> super_lock(self, super); - - if (!super->IsVerified() && !super->IsErroneous()) { - VerifyClass(self, super); - } - if (!super->IsCompileTimeVerified()) { - std::string error_msg( - StringPrintf("Rejecting class %s that attempts to sub-class erroneous class %s", - PrettyDescriptor(klass.Get()).c_str(), - PrettyDescriptor(super.Get()).c_str())); - LOG(WARNING) << error_msg << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); - Handle<mirror::Throwable> cause(hs.NewHandle(self->GetException())); - if (cause.Get() != nullptr) { - self->ClearException(); - } - ThrowVerifyError(klass.Get(), "%s", error_msg.c_str()); - if (cause.Get() != nullptr) { - self->GetException()->SetCause(cause.Get()); - } - ClassReference ref(klass->GetDexCache()->GetDexFile(), klass->GetDexClassDefIndex()); - if (Runtime::Current()->IsAotCompiler()) { - Runtime::Current()->GetCompilerCallbacks()->ClassRejected(ref); + MutableHandle<mirror::Class> supertype(hs.NewHandle(klass->GetSuperClass())); + // If we have a superclass and we get a hard verification failure we can return immediately. + if (supertype.Get() != nullptr && !AttemptSupertypeVerification(self, klass, supertype)) { + CHECK(self->IsExceptionPending()) << "Verification error should be pending."; + return; + } + + // Verify all default super-interfaces. + // + // (1) Don't bother if the superclass has already had a soft verification failure. + // + // (2) Interfaces shouldn't bother to do this recursive verification because they cannot cause + // recursive initialization by themselves. This is because when an interface is initialized + // directly it must not initialize its superinterfaces. We are allowed to verify regardless + // but choose not to for an optimization. If the interfaces is being verified due to a class + // initialization (which would need all the default interfaces to be verified) the class code + // will trigger the recursive verification anyway. + if ((supertype.Get() == nullptr || supertype->IsVerified()) // See (1) + && !klass->IsInterface()) { // See (2) + int32_t iftable_count = klass->GetIfTableCount(); + MutableHandle<mirror::Class> iface(hs.NewHandle<mirror::Class>(nullptr)); + // Loop through all interfaces this class has defined. It doesn't matter the order. + for (int32_t i = 0; i < iftable_count; i++) { + iface.Assign(klass->GetIfTable()->GetInterface(i)); + DCHECK(iface.Get() != nullptr); + // We only care if we have default interfaces and can skip if we are already verified... + if (LIKELY(!iface->HasDefaultMethods() || iface->IsVerified())) { + continue; + } else if (UNLIKELY(!AttemptSupertypeVerification(self, klass, iface))) { + // We had a hard failure while verifying this interface. Just return immediately. + CHECK(self->IsExceptionPending()) << "Verification error should be pending."; + return; + } else if (UNLIKELY(!iface->IsVerified())) { + // We softly failed to verify the iface. Stop checking and clean up. + // Put the iface into the supertype handle so we know what caused us to fail. + supertype.Assign(iface.Get()); + break; } - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); - return; } } + // At this point if verification failed, then supertype is the "first" supertype that failed + // verification (without a specific order). If verification succeeded, then supertype is either + // null or the original superclass of klass and is verified. + DCHECK(supertype.Get() == nullptr || + supertype.Get() == klass->GetSuperClass() || + !supertype->IsVerified()); + // Try to use verification information from the oat file, otherwise do runtime verification. const DexFile& dex_file = *klass->GetDexCache()->GetDexFile(); mirror::Class::Status oat_file_class_status(mirror::Class::kStatusNotReady); bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status); - if (oat_file_class_status == mirror::Class::kStatusError) { - VLOG(class_linker) << "Skipping runtime verification of erroneous class " - << PrettyDescriptor(klass.Get()) << " in " - << klass->GetDexCache()->GetLocation()->ToModifiedUtf8(); - ThrowVerifyError(klass.Get(), "Rejecting class %s because it failed compile-time verification", - PrettyDescriptor(klass.Get()).c_str()); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); - return; - } + // If the oat file says the class had an error, re-run the verifier. That way we will get a + // precise error message. To ensure a rerun, test: + // oat_file_class_status == mirror::Class::kStatusError => !preverified + DCHECK(!(oat_file_class_status == mirror::Class::kStatusError) || !preverified); + verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure; std::string error_msg; if (!preverified) { verifier_failure = verifier::MethodVerifier::VerifyClass(self, klass.Get(), Runtime::Current()->IsAotCompiler(), + Runtime::Current()->IsAotCompiler(), &error_msg); } if (preverified || verifier_failure != verifier::MethodVerifier::kHardFailure) { @@ -2937,14 +3008,14 @@ void ClassLinker::VerifyClass(Thread* self, Handle<mirror::Class> klass) { // Make sure all classes referenced by catch blocks are resolved. ResolveClassExceptionHandlerTypes(dex_file, klass); if (verifier_failure == verifier::MethodVerifier::kNoFailure) { - // Even though there were no verifier failures we need to respect whether the super-class - // was verified or requiring runtime reverification. - if (super.Get() == nullptr || super->IsVerified()) { + // Even though there were no verifier failures we need to respect whether the super-class and + // super-default-interfaces were verified or requiring runtime reverification. + if (supertype.Get() == nullptr || supertype->IsVerified()) { mirror::Class::SetStatus(klass, mirror::Class::kStatusVerified, self); } else { - CHECK_EQ(super->GetStatus(), mirror::Class::kStatusRetryVerificationAtRuntime); + CHECK_EQ(supertype->GetStatus(), mirror::Class::kStatusRetryVerificationAtRuntime); mirror::Class::SetStatus(klass, mirror::Class::kStatusRetryVerificationAtRuntime, self); - // Pretend a soft failure occured so that we don't consider the class verified below. + // Pretend a soft failure occurred so that we don't consider the class verified below. verifier_failure = verifier::MethodVerifier::kSoftFailure; } } else { @@ -2962,9 +3033,9 @@ void ClassLinker::VerifyClass(Thread* self, Handle<mirror::Class> klass) { } } } else { - LOG(WARNING) << "Verification failed on class " << PrettyDescriptor(klass.Get()) - << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8() - << " because: " << error_msg; + VLOG(verifier) << "Verification failed on class " << PrettyDescriptor(klass.Get()) + << " in " << klass->GetDexCache()->GetLocation()->ToModifiedUtf8() + << " because: " << error_msg; self->AssertNoPendingException(); ThrowVerifyError(klass.Get(), "%s", error_msg.c_str()); mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); @@ -3348,7 +3419,7 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons // Basic sanity CHECK(!prototype->IsFinal()); CHECK(method->IsFinal()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); // The proxy method doesn't have its own dex cache or dex file and so it steals those of its // interface prototype. The exception to this are Constructors and the Class of the Proxy itself. @@ -4079,10 +4150,10 @@ bool ClassLinker::LinkClass(Thread* self, Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader); } CHECK_EQ(existing, klass.Get()); - if (kIsDebugBuild && class_loader == nullptr && dex_cache_image_class_lookup_required_) { + if (kIsDebugBuild && class_loader == nullptr && dex_cache_boot_image_class_lookup_required_) { // Check a class loaded with the system class loader matches one in the image if the class // is in the image. - mirror::Class* const image_class = LookupClassFromImage(descriptor); + mirror::Class* const image_class = LookupClassFromBootImage(descriptor); if (image_class != nullptr) { CHECK_EQ(klass.Get(), existing) << descriptor; } @@ -4424,7 +4495,7 @@ bool ClassLinker::LinkMethods(Thread* self, // A map from vtable indexes to the method they need to be updated to point to. Used because we // need to have default methods be in the virtuals array of each class but we don't set that up // until LinkInterfaceMethods. - std::unordered_map<size_t, ArtMethod*> default_translations; + std::unordered_map<size_t, ClassLinker::MethodTranslation> default_translations; // Link virtual methods then interface methods. // We set up the interface lookup table first because we need it to determine if we need to update // any vtable entries with new default method implementations. @@ -4557,7 +4628,7 @@ const uint32_t LinkVirtualHashTable::removed_index_ = std::numeric_limits<uint32 bool ClassLinker::LinkVirtualMethods( Thread* self, Handle<mirror::Class> klass, - /*out*/std::unordered_map<size_t, ArtMethod*>* default_translations) { + /*out*/std::unordered_map<size_t, ClassLinker::MethodTranslation>* default_translations) { const size_t num_virtual_methods = klass->NumVirtualMethods(); if (klass->IsInterface()) { // No vtable. @@ -4680,46 +4751,55 @@ bool ClassLinker::LinkVirtualMethods( << " would have incorrectly overridden the package-private method in " << PrettyDescriptor(super_method->GetDeclaringClassDescriptor()); } - } else if (super_method->IsDefault()) { + } else if (super_method->IsOverridableByDefaultMethod()) { // We didn't directly override this method but we might through default methods... // Check for default method update. ArtMethod* default_method = nullptr; - std::string icce_message; - if (!FindDefaultMethodImplementation(self, - super_method, - klass, - /*out*/&default_method, - /*out*/&icce_message)) { - // An error occurred while finding default methods. - // TODO This should actually be thrown when we attempt to invoke this method. - ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str()); - return false; - } - // This should always work because we inherit superclass interfaces. We should either get - // 1) An IncompatibleClassChangeError because of conflicting default method - // implementations. - // 2) The same default method implementation as the superclass. - // 3) A default method that overrides the superclass's. - // Therefore this check should never fail. - CHECK(default_method != nullptr); - if (UNLIKELY(default_method->GetDeclaringClass() != super_method->GetDeclaringClass())) { - // TODO Refactor this add default methods to virtuals here and not in - // LinkInterfaceMethods maybe. - // The problem is default methods might override previously present default-method or - // miranda-method vtable entries from the superclass. Unfortunately we need these to - // be entries in this class's virtuals. We do not give these entries there until - // LinkInterfaceMethods so we pass this map around to let it know which vtable - // entries need to be updated. - // Make a note that vtable entry j must be updated, store what it needs to be updated to. - // We will allocate a virtual method slot in LinkInterfaceMethods and fix it up then. - default_translations->insert({j, default_method}); - VLOG(class_linker) << "Method " << PrettyMethod(super_method) << " overridden by default " - << PrettyMethod(default_method) << " in " << PrettyClass(klass.Get()); - } else { - // They are the same method/no override - // Cannot do direct comparison because we had to copy the ArtMethod object into the - // superclass's vtable. - continue; + switch (FindDefaultMethodImplementation(self, + super_method, + klass, + /*out*/&default_method)) { + case DefaultMethodSearchResult::kDefaultConflict: { + // A conflict was found looking for default methods. Note this (assuming it wasn't + // pre-existing) in the translations map. + if (UNLIKELY(!super_method->IsDefaultConflicting())) { + // Don't generate another conflict method to reduce memory use as an optimization. + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateConflictingMethod()}); + } + break; + } + case DefaultMethodSearchResult::kAbstractFound: { + // No conflict but method is abstract. + // We note that this vtable entry must be made abstract. + if (UNLIKELY(!super_method->IsAbstract())) { + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateAbstractMethod()}); + } + break; + } + case DefaultMethodSearchResult::kDefaultFound: { + if (UNLIKELY(super_method->IsDefaultConflicting() || + default_method->GetDeclaringClass() != super_method->GetDeclaringClass())) { + // Found a default method implementation that is new. + // TODO Refactor this add default methods to virtuals here and not in + // LinkInterfaceMethods maybe. + // The problem is default methods might override previously present + // default-method or miranda-method vtable entries from the superclass. + // Unfortunately we need these to be entries in this class's virtuals. We do not + // give these entries there until LinkInterfaceMethods so we pass this map around + // to let it know which vtable entries need to be updated. + // Make a note that vtable entry j must be updated, store what it needs to be updated + // to. We will allocate a virtual method slot in LinkInterfaceMethods and fix it up + // then. + default_translations->insert( + {j, ClassLinker::MethodTranslation::CreateTranslatedMethod(default_method)}); + VLOG(class_linker) << "Method " << PrettyMethod(super_method) + << " overridden by default " << PrettyMethod(default_method) + << " in " << PrettyClass(klass.Get()); + } + break; + } } } } @@ -4772,23 +4852,75 @@ bool ClassLinker::LinkVirtualMethods( return true; } +// Determine if the given iface has any subinterface in the given list that declares the method +// specified by 'target'. +// +// Arguments +// - self: The thread we are running on +// - target: A comparator that will match any method that overrides the method we are checking for +// - iftable: The iftable we are searching for an overriding method on. +// - ifstart: The index of the interface we are checking to see if anything overrides +// - iface: The interface we are checking to see if anything overrides. +// - image_pointer_size: +// The image pointer size. +// +// Returns +// - True: There is some method that matches the target comparator defined in an interface that +// is a subtype of iface. +// - False: There is no method that matches the target comparator in any interface that is a subtype +// of iface. +static bool ContainsOverridingMethodOf(Thread* self, + MethodNameAndSignatureComparator& target, + Handle<mirror::IfTable> iftable, + size_t ifstart, + Handle<mirror::Class> iface, + size_t image_pointer_size) + SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK(self != nullptr); + DCHECK(iface.Get() != nullptr); + DCHECK(iftable.Get() != nullptr); + DCHECK_GE(ifstart, 0u); + DCHECK_LT(ifstart, iftable->Count()); + DCHECK_EQ(iface.Get(), iftable->GetInterface(ifstart)); + DCHECK(iface->IsInterface()); + + size_t iftable_count = iftable->Count(); + StackHandleScope<1> hs(self); + MutableHandle<mirror::Class> current_iface(hs.NewHandle<mirror::Class>(nullptr)); + for (size_t k = ifstart + 1; k < iftable_count; k++) { + // Skip ifstart since our current interface obviously cannot override itself. + current_iface.Assign(iftable->GetInterface(k)); + size_t num_instance_methods = current_iface->NumVirtualMethods(); + // Iterate through every method on this interface. The order does not matter so we go forwards. + for (size_t m = 0; m < num_instance_methods; m++) { + ArtMethod* current_method = current_iface->GetVirtualMethodUnchecked(m, image_pointer_size); + if (UNLIKELY(target.HasSameNameAndSignature( + current_method->GetInterfaceMethodIfProxy(image_pointer_size)))) { + // Check if the i'th interface is a subtype of this one. + if (iface->IsAssignableFrom(current_iface.Get())) { + return true; + } + break; + } + } + } + return false; +} + // Find the default method implementation for 'interface_method' in 'klass'. Stores it into -// out_default_method and returns true on success. If no default method was found stores nullptr -// into out_default_method and returns true. If an error occurs (such as a default_method conflict) -// it will fill the icce_message with an appropriate message for an IncompatibleClassChangeError, -// which should then be thrown by the caller. -bool ClassLinker::FindDefaultMethodImplementation(Thread* self, - ArtMethod* target_method, - Handle<mirror::Class> klass, - /*out*/ArtMethod** out_default_method, - /*out*/std::string* icce_message) const { +// out_default_method and returns kDefaultFound on success. If no default method was found return +// kAbstractFound and store nullptr into out_default_method. If an error occurs (such as a +// default_method conflict) it will return kDefaultConflict. +ClassLinker::DefaultMethodSearchResult ClassLinker::FindDefaultMethodImplementation( + Thread* self, + ArtMethod* target_method, + Handle<mirror::Class> klass, + /*out*/ArtMethod** out_default_method) const { DCHECK(self != nullptr); DCHECK(target_method != nullptr); DCHECK(out_default_method != nullptr); - DCHECK(icce_message != nullptr); *out_default_method = nullptr; - mirror::Class* chosen_iface = nullptr; // We organize the interface table so that, for interface I any subinterfaces J follow it in the // table. This lets us walk the table backwards when searching for default methods. The first one @@ -4799,19 +4931,23 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, // The order of unrelated interfaces does not matter and is not defined. size_t iftable_count = klass->GetIfTableCount(); if (iftable_count == 0) { - // No interfaces. We have already reset out to null so just return true. - return true; + // No interfaces. We have already reset out to null so just return kAbstractFound. + return DefaultMethodSearchResult::kAbstractFound; } - StackHandleScope<1> hs(self); + StackHandleScope<3> hs(self); + MutableHandle<mirror::Class> chosen_iface(hs.NewHandle<mirror::Class>(nullptr)); MutableHandle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); + MutableHandle<mirror::Class> iface(hs.NewHandle<mirror::Class>(nullptr)); MethodNameAndSignatureComparator target_name_comparator( target_method->GetInterfaceMethodIfProxy(image_pointer_size_)); // Iterates over the klass's iftable in reverse - // We have a break at the end because size_t is unsigned. - for (size_t k = iftable_count - 1; /* break if k == 0 at end */; --k) { + for (size_t k = iftable_count; k != 0; ) { + --k; + DCHECK_LT(k, iftable->Count()); - mirror::Class* iface = iftable->GetInterface(k); + + iface.Assign(iftable->GetInterface(k)); size_t num_instance_methods = iface->NumVirtualMethods(); // Iterate through every method on this interface. The order does not matter so we go forwards. for (size_t m = 0; m < num_instance_methods; m++) { @@ -4824,31 +4960,60 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, } // The verifier should have caught the non-public method. DCHECK(current_method->IsPublic()) << "Interface method is not public!"; - if (UNLIKELY(chosen_iface != nullptr)) { - // We have multiple default impls of the same method. We need to check they do not - // conflict and throw an error if they do. Conflicting means that the current iface is not - // masked by the chosen interface. - if (!iface->IsAssignableFrom(chosen_iface)) { - *icce_message = StringPrintf("Conflicting default method implementations: '%s' and '%s'", - PrettyMethod(current_method).c_str(), - PrettyMethod(*out_default_method).c_str()); - return false; + if (UNLIKELY(chosen_iface.Get() != nullptr)) { + // We have multiple default impls of the same method. This is a potential default conflict. + // We need to check if this possibly conflicting method is either a superclass of the chosen + // default implementation or is overridden by a non-default interface method. In either case + // there is no conflict. + if (!iface->IsAssignableFrom(chosen_iface.Get()) && + !ContainsOverridingMethodOf(self, + target_name_comparator, + iftable, + k, + iface, + image_pointer_size_)) { + LOG(WARNING) << "Conflicting default method implementations found: " + << PrettyMethod(current_method) << " and " + << PrettyMethod(*out_default_method) << " in class " + << PrettyClass(klass.Get()) << " conflict."; + *out_default_method = nullptr; + return DefaultMethodSearchResult::kDefaultConflict; } else { break; // Continue checking at the next interface. } } else { - *out_default_method = current_method; - chosen_iface = iface; - // We should now finish traversing the graph to find if we have default methods that - // conflict. - break; + // chosen_iface == null + if (!ContainsOverridingMethodOf(self, + target_name_comparator, + iftable, + k, + iface, + image_pointer_size_)) { + // Don't set this as the chosen interface if something else is overriding it (because that + // other interface would be potentially chosen instead if it was default). If the other + // interface was abstract then we wouldn't select this interface as chosen anyway since + // the abstract method masks it. + *out_default_method = current_method; + chosen_iface.Assign(iface.Get()); + // We should now finish traversing the graph to find if we have default methods that + // conflict. + } else { + VLOG(class_linker) << "A default method '" << PrettyMethod(current_method) << "' was " + << "skipped because it was overridden by an abstract method in a " + << "subinterface on class '" << PrettyClass(klass.Get()) << "'"; + } } - } - if (k == 0) { break; } } - return true; + if (*out_default_method != nullptr) { + VLOG(class_linker) << "Default method '" << PrettyMethod(*out_default_method) << "' selected " + << "as the implementation for '" << PrettyMethod(target_method) << "' " + << "in '" << PrettyClass(klass.Get()) << "'"; + return DefaultMethodSearchResult::kDefaultFound; + } else { + return DefaultMethodSearchResult::kAbstractFound; + } } // Sets imt_ref appropriately for LinkInterfaceMethods. @@ -4856,7 +5021,7 @@ bool ClassLinker::FindDefaultMethodImplementation(Thread* self, // Otherwise it will set the conflict method which will figure out which method to use during // runtime. static void SetIMTRef(ArtMethod* unimplemented_method, - ArtMethod* conflict_method, + ArtMethod* imt_conflict_method, size_t image_pointer_size, ArtMethod* current_method, /*out*/ArtMethod** imt_ref) @@ -4864,7 +5029,7 @@ static void SetIMTRef(ArtMethod* unimplemented_method, // Place method in imt if entry is empty, place conflict otherwise. if (*imt_ref == unimplemented_method) { *imt_ref = current_method; - } else if (*imt_ref != conflict_method) { + } else if (*imt_ref != imt_conflict_method) { // If we are not a conflict and we have the same signature and name as the imt // entry, it must be that we overwrote a superclass vtable entry. MethodNameAndSignatureComparator imt_comparator( @@ -4873,7 +5038,7 @@ static void SetIMTRef(ArtMethod* unimplemented_method, current_method->GetInterfaceMethodIfProxy(image_pointer_size))) { *imt_ref = current_method; } else { - *imt_ref = conflict_method; + *imt_ref = imt_conflict_method; } } } @@ -5080,10 +5245,23 @@ bool ClassLinker::SetupInterfaceLookupTable(Thread* self, Handle<mirror::Class> return true; } +// Finds the method with a name/signature that matches cmp in the given list of methods. The list of +// methods must be unique. +static ArtMethod* FindSameNameAndSignature(MethodNameAndSignatureComparator& cmp, + const ScopedArenaVector<ArtMethod*>& list) + SHARED_REQUIRES(Locks::mutator_lock_) { + for (ArtMethod* method : list) { + if (cmp.HasSameNameAndSignature(method)) { + return method; + } + } + return nullptr; +} + bool ClassLinker::LinkInterfaceMethods( Thread* self, Handle<mirror::Class> klass, - const std::unordered_map<size_t, ArtMethod*>& default_translations, + const std::unordered_map<size_t, ClassLinker::MethodTranslation>& default_translations, ArtMethod** out_imt) { StackHandleScope<3> hs(self); Runtime* const runtime = Runtime::Current(); @@ -5106,12 +5284,14 @@ bool ClassLinker::LinkInterfaceMethods( // Use the linear alloc pool since this one is in the low 4gb for the compiler. ArenaStack stack(runtime->GetLinearAlloc()->GetArenaPool()); ScopedArenaAllocator allocator(&stack); + + ScopedArenaVector<ArtMethod*> default_conflict_methods(allocator.Adapter()); ScopedArenaVector<ArtMethod*> miranda_methods(allocator.Adapter()); ScopedArenaVector<ArtMethod*> default_methods(allocator.Adapter()); MutableHandle<mirror::PointerArray> vtable(hs.NewHandle(klass->GetVTableDuringLinking())); ArtMethod* const unimplemented_method = runtime->GetImtUnimplementedMethod(); - ArtMethod* const conflict_method = runtime->GetImtConflictMethod(); + ArtMethod* const imt_conflict_method = runtime->GetImtConflictMethod(); // Copy the IMT from the super class if possible. bool extend_super_iftable = false; if (has_superclass) { @@ -5147,8 +5327,8 @@ bool ClassLinker::LinkInterfaceMethods( auto** imt_ref = &out_imt[imt_index]; if (*imt_ref == unimplemented_method) { *imt_ref = method; - } else if (*imt_ref != conflict_method) { - *imt_ref = conflict_method; + } else if (*imt_ref != imt_conflict_method) { + *imt_ref = imt_conflict_method; } } } @@ -5183,7 +5363,16 @@ bool ClassLinker::LinkInterfaceMethods( auto* old_cause = self->StartAssertNoThreadSuspension( "Copying ArtMethods for LinkInterfaceMethods"); - for (size_t i = 0; i < ifcount; ++i) { + // Going in reverse to ensure that we will hit abstract methods that override defaults before the + // defaults. This means we don't need to do any trickery when creating the Miranda methods, since + // they will already be null. This has the additional benefit that the declarer of a miranda + // method will actually declare an abstract method. + for (size_t i = ifcount; i != 0; ) { + --i; + + DCHECK_GE(i, 0u); + DCHECK_LT(i, ifcount); + size_t num_methods = iftable->GetInterface(i)->NumVirtualMethods(); if (num_methods > 0) { StackHandleScope<2> hs2(self); @@ -5194,6 +5383,11 @@ bool ClassLinker::LinkInterfaceMethods( LengthPrefixedArray<ArtMethod>* input_virtual_methods = nullptr; Handle<mirror::PointerArray> input_vtable_array = NullHandle<mirror::PointerArray>(); int32_t input_array_length = 0; + // TODO Cleanup Needed: In the presence of default methods this optimization is rather dirty + // and confusing. Default methods should always look through all the superclasses + // because they are the last choice of an implementation. We get around this by looking + // at the super-classes iftable methods (copied into method_array previously) when we are + // looking for the implementation of a super-interface method but that is rather dirty. if (super_interface) { // We are overwriting a super class interface, try to only virtual methods instead of the // whole vtable. @@ -5223,8 +5417,7 @@ bool ClassLinker::LinkInterfaceMethods( // // To find defaults we need to do the same but also go over interfaces. bool found_impl = false; - ArtMethod* default_impl = nullptr; - bool found_default_impl = false; + ArtMethod* vtable_impl = nullptr; for (int32_t k = input_array_length - 1; k >= 0; --k) { ArtMethod* vtable_method = input_virtual_methods != nullptr ? &input_virtual_methods->At(k, method_size, method_alignment) : @@ -5241,77 +5434,138 @@ bool ClassLinker::LinkInterfaceMethods( "Method '%s' implementing interface method '%s' is not public", PrettyMethod(vtable_method).c_str(), PrettyMethod(interface_method).c_str()); return false; - } else if (vtable_method->IsDefault()) { + } else if (UNLIKELY(vtable_method->IsOverridableByDefaultMethod())) { // We might have a newer, better, default method for this, so we just skip it. If we // are still using this we will select it again when scanning for default methods. To // obviate the need to copy the method again we will make a note that we already found // a default here. // TODO This should be much cleaner. - found_default_impl = true; - default_impl = vtable_method; + vtable_impl = vtable_method; break; } else { found_impl = true; + method_array->SetElementPtrSize(j, vtable_method, image_pointer_size_); + // Place method in imt if entry is empty, place conflict otherwise. + SetIMTRef(unimplemented_method, + imt_conflict_method, + image_pointer_size_, + vtable_method, + /*out*/imt_ptr); + break; } - method_array->SetElementPtrSize(j, vtable_method, image_pointer_size_); - // Place method in imt if entry is empty, place conflict otherwise. - SetIMTRef(unimplemented_method, - conflict_method, - image_pointer_size_, - vtable_method, - /*out*/imt_ptr); - break; } } - // We should only search for default implementations when the class does not implement the - // method directly and either (1) the interface is newly implemented on this class and not - // on any of its superclasses, (2) the superclass's implementation is a default method, or - // (3) the superclass does not have an implementation. - if (!found_impl && (!super_interface || - method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_) - ->IsOverridableByDefaultMethod())) { - ArtMethod* current_method = nullptr; - std::string icce_message; - if (!FindDefaultMethodImplementation(self, - interface_method, - klass, - /*out*/¤t_method, - /*out*/&icce_message)) { - // There was a conflict with default method implementations. - self->EndAssertNoThreadSuspension(old_cause); - // TODO This should actually be thrown when we attempt to invoke this method. - ThrowIncompatibleClassChangeError(klass.Get(), "%s", icce_message.c_str()); - return false; - } else if (current_method != nullptr) { - if (found_default_impl && - current_method->GetDeclaringClass() == default_impl->GetDeclaringClass()) { + // Continue on to the next method if we are done. + if (LIKELY(found_impl)) { + continue; + } else if (LIKELY(super_interface)) { + // Don't look for a default implementation when the super-method is implemented directly + // by the class. + // + // See if we can use the superclasses method and skip searching everything else. + // Note: !found_impl && super_interface + CHECK(extend_super_iftable); + // If this is a super_interface method it is possible we shouldn't override it because a + // superclass could have implemented it directly. We get the method the superclass used + // to implement this to know if we can override it with a default method. Doing this is + // safe since we know that the super_iftable is filled in so we can simply pull it from + // there. We don't bother if this is not a super-classes interface since in that case we + // have scanned the entire vtable anyway and would have found it. + // TODO This is rather dirty but it is faster than searching through the entire vtable + // every time. + ArtMethod* supers_method = + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); + DCHECK(supers_method != nullptr); + DCHECK(interface_name_comparator.HasSameNameAndSignature(supers_method)); + if (!supers_method->IsOverridableByDefaultMethod()) { + // The method is not overridable by a default method (i.e. it is directly implemented + // in some class). Therefore move onto the next interface method. + continue; + } + } + // If we haven't found it yet we should search through the interfaces for default methods. + ArtMethod* current_method = nullptr; + switch (FindDefaultMethodImplementation(self, + interface_method, + klass, + /*out*/¤t_method)) { + case DefaultMethodSearchResult::kDefaultConflict: { + // Default method conflict. + DCHECK(current_method == nullptr); + ArtMethod* default_conflict_method = nullptr; + if (vtable_impl != nullptr && vtable_impl->IsDefaultConflicting()) { + // We can reuse the method from the superclass, don't bother adding it to virtuals. + default_conflict_method = vtable_impl; + } else { + // See if we already have a conflict method for this method. + ArtMethod* preexisting_conflict = FindSameNameAndSignature(interface_name_comparator, + default_conflict_methods); + if (LIKELY(preexisting_conflict != nullptr)) { + // We already have another conflict we can reuse. + default_conflict_method = preexisting_conflict; + } else { + // Create a new conflict method for this to use. + default_conflict_method = + reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); + new(default_conflict_method) ArtMethod(interface_method, image_pointer_size_); + default_conflict_methods.push_back(default_conflict_method); + } + } + current_method = default_conflict_method; + break; + } + case DefaultMethodSearchResult::kDefaultFound: { + DCHECK(current_method != nullptr); + // Found a default method. + if (vtable_impl != nullptr && + current_method->GetDeclaringClass() == vtable_impl->GetDeclaringClass()) { // We found a default method but it was the same one we already have from our // superclass. Don't bother adding it to our vtable again. - current_method = default_impl; + current_method = vtable_impl; } else { - // We found a default method implementation and there were no conflicts. - // Save the default method. We need to add it to the vtable. - default_methods.push_back(current_method); + // Only record this default method if it is new to save space. + ArtMethod* old = FindSameNameAndSignature(interface_name_comparator, default_methods); + if (old == nullptr) { + // We found a default method implementation and there were no conflicts. + // Save the default method. We need to add it to the vtable. + default_methods.push_back(current_method); + } else { + CHECK(old == current_method) << "Multiple default implementations selected!"; + } } - method_array->SetElementPtrSize(j, current_method, image_pointer_size_); - SetIMTRef(unimplemented_method, - conflict_method, - image_pointer_size_, - current_method, - /*out*/imt_ptr); - found_impl = true; + break; } - } - if (!found_impl && !super_interface) { - // It is defined in this class or any of its subclasses. - ArtMethod* miranda_method = nullptr; - for (auto& mir_method : miranda_methods) { - if (interface_name_comparator.HasSameNameAndSignature(mir_method)) { - miranda_method = mir_method; - break; + case DefaultMethodSearchResult::kAbstractFound: { + DCHECK(current_method == nullptr); + // Abstract method masks all defaults. + if (vtable_impl != nullptr && + vtable_impl->IsAbstract() && + !vtable_impl->IsDefaultConflicting()) { + // We need to make this an abstract method but the version in the vtable already is so + // don't do anything. + current_method = vtable_impl; } + break; } + } + if (current_method != nullptr) { + // We found a default method implementation. Record it in the iftable and IMT. + method_array->SetElementPtrSize(j, current_method, image_pointer_size_); + SetIMTRef(unimplemented_method, + imt_conflict_method, + image_pointer_size_, + current_method, + /*out*/imt_ptr); + } else if (!super_interface) { + // We could not find an implementation for this method and since it is a brand new + // interface we searched the entire vtable (and all default methods) for an implementation + // but couldn't find one. We therefore need to make a miranda method. + // + // Find out if there is already a miranda method we can use. + ArtMethod* miranda_method = FindSameNameAndSignature(interface_name_comparator, + miranda_methods); if (miranda_method == nullptr) { + DCHECK(interface_method->IsAbstract()) << PrettyMethod(interface_method); miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); CHECK(miranda_method != nullptr); // Point the interface table at a phantom slot. @@ -5323,10 +5577,15 @@ bool ClassLinker::LinkInterfaceMethods( } } } - if (!miranda_methods.empty() || !default_methods.empty()) { + if (!miranda_methods.empty() || !default_methods.empty() || !default_conflict_methods.empty()) { + VLOG(class_linker) << PrettyClass(klass.Get()) << ": miranda_methods=" << miranda_methods.size() + << " default_methods=" << default_methods.size() + << " default_conflict_methods=" << default_conflict_methods.size(); const size_t old_method_count = klass->NumVirtualMethods(); - const size_t new_method_count = - old_method_count + miranda_methods.size() + default_methods.size(); + const size_t new_method_count = old_method_count + + miranda_methods.size() + + default_methods.size() + + default_conflict_methods.size(); // Attempt to realloc to save RAM if possible. LengthPrefixedArray<ArtMethod>* old_virtuals = klass->GetVirtualMethodsPtr(); // The Realloced virtual methods aren't visiblef from the class roots, so there is no issue @@ -5384,15 +5643,32 @@ bool ClassLinker::LinkInterfaceMethods( for (ArtMethod* def_method : default_methods) { ArtMethod& new_method = *out; new_method.CopyFrom(def_method, image_pointer_size_); - new_method.SetAccessFlags(new_method.GetAccessFlags() | kAccDefault); // Clear the preverified flag if it is present. Since this class hasn't been verified yet it // shouldn't have methods that are preverified. // TODO This is rather arbitrary. We should maybe support classes where only some of its // methods are preverified. - new_method.SetAccessFlags(new_method.GetAccessFlags() & ~kAccPreverified); + new_method.SetAccessFlags((new_method.GetAccessFlags() | kAccDefault) & ~kAccPreverified); move_table.emplace(def_method, &new_method); ++out; } + for (ArtMethod* conf_method : default_conflict_methods) { + ArtMethod& new_method = *out; + new_method.CopyFrom(conf_method, image_pointer_size_); + // This is a type of default method (there are default method impls, just a conflict) so mark + // this as a default, non-abstract method, since thats what it is. Also clear the preverified + // bit since this class hasn't been verified yet it shouldn't have methods that are + // preverified. + constexpr uint32_t kSetFlags = kAccDefault | kAccDefaultConflict; + constexpr uint32_t kMaskFlags = ~(kAccAbstract | kAccPreverified); + new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); + DCHECK(new_method.IsDefaultConflicting()); + // The actual method might or might not be marked abstract since we just copied it from a + // (possibly default) interface method. We need to set it entry point to be the bridge so that + // the compiler will not invoke the implementation of whatever method we copied from. + EnsureThrowsInvocationError(&new_method); + move_table.emplace(conf_method, &new_method); + ++out; + } virtuals->SetSize(new_method_count); UpdateClassVirtualMethods(klass.Get(), virtuals); // Done copying methods, they are all roots in the class now, so we can end the no thread @@ -5400,8 +5676,10 @@ bool ClassLinker::LinkInterfaceMethods( self->EndAssertNoThreadSuspension(old_cause); const size_t old_vtable_count = vtable->GetLength(); - const size_t new_vtable_count = - old_vtable_count + miranda_methods.size() + default_methods.size(); + const size_t new_vtable_count = old_vtable_count + + miranda_methods.size() + + default_methods.size() + + default_conflict_methods.size(); miranda_methods.clear(); vtable.Assign(down_cast<mirror::PointerArray*>(vtable->CopyOf(self, new_vtable_count))); if (UNLIKELY(vtable.Get() == nullptr)) { @@ -5426,9 +5704,27 @@ bool ClassLinker::LinkInterfaceMethods( auto translation_it = default_translations.find(i); bool found_translation = false; if (translation_it != default_translations.end()) { - size_t vtable_index; - std::tie(vtable_index, translated_method) = *translation_it; - DCHECK_EQ(vtable_index, i); + if (translation_it->second.IsInConflict()) { + // Find which conflict method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); + ArtMethod* new_conflict_method = FindSameNameAndSignature(old_method_comparator, + default_conflict_methods); + CHECK(new_conflict_method != nullptr) << "Expected a conflict method!"; + translated_method = new_conflict_method; + } else if (translation_it->second.IsAbstract()) { + // Find which miranda method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); + ArtMethod* miranda_method = FindSameNameAndSignature(old_method_comparator, + miranda_methods); + DCHECK(miranda_method != nullptr); + translated_method = miranda_method; + } else { + // Normal default method (changed from an older default or abstract interface method). + DCHECK(translation_it->second.IsTranslation()); + translated_method = translation_it->second.GetTranslation(); + } found_translation = true; } DCHECK(translated_method != nullptr); @@ -6135,8 +6431,8 @@ void ClassLinker::SetEntryPointsToInterpreter(ArtMethod* method) const { void ClassLinker::DumpForSigQuit(std::ostream& os) { ScopedObjectAccess soa(Thread::Current()); - if (dex_cache_image_class_lookup_required_) { - MoveImageClassesToClassTable(); + if (dex_cache_boot_image_class_lookup_required_) { + AddBootImageClassesToClassTable(); } ReaderMutexLock mu(soa.Self(), *Locks::classlinker_classes_lock_); os << "Zygote loaded classes=" << NumZygoteClasses() << " post zygote classes=" @@ -6173,8 +6469,8 @@ size_t ClassLinker::NumNonZygoteClasses() const { } size_t ClassLinker::NumLoadedClasses() { - if (dex_cache_image_class_lookup_required_) { - MoveImageClassesToClassTable(); + if (dex_cache_boot_image_class_lookup_required_) { + AddBootImageClassesToClassTable(); } ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); // Only return non zygote classes since these are the ones which apps which care about. diff --git a/runtime/class_linker.h b/runtime/class_linker.h index a35ba3e6ef..21f9e7b73a 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -487,10 +487,17 @@ class ClassLinker { return class_roots; } - // Move all of the image classes into the class table for faster lookups. - void MoveImageClassesToClassTable() + // Move all of the boot image classes into the class table for faster lookups. + void AddBootImageClassesToClassTable() REQUIRES(!Locks::classlinker_classes_lock_) SHARED_REQUIRES(Locks::mutator_lock_); + + // Add image classes to the class table. + void AddImageClassesToClassTable(gc::space::ImageSpace* image_space, + mirror::ClassLoader* class_loader) + REQUIRES(!Locks::classlinker_classes_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + // Move the class table to the pre-zygote table to reduce memory usage. This works by ensuring // that no more classes are ever added to the pre zygote table which makes it that the pages // always remain shared dirty instead of private dirty. @@ -551,6 +558,15 @@ class ClassLinker { LinearAlloc* allocator; }; + // Ensures that the supertype of 'klass' ('supertype') is verified. Returns false and throws + // appropriate exceptions if verification failed hard. Returns true for successful verification or + // soft-failures. + bool AttemptSupertypeVerification(Thread* self, + Handle<mirror::Class> klass, + Handle<mirror::Class> supertype) + REQUIRES(!dex_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + static void DeleteClassLoader(Thread* self, const ClassLoaderData& data) REQUIRES(Locks::classlinker_classes_lock_) SHARED_REQUIRES(Locks::mutator_lock_); @@ -572,7 +588,7 @@ class ClassLinker { SHARED_REQUIRES(Locks::mutator_lock_); void FinishInit(Thread* self) - SHARED_REQUIRES(Locks::mutator_lock_) + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_, !Roles::uninterruptible_); // For early bootstrapping by Init @@ -712,6 +728,83 @@ class ClassLinker { ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); + // Does anything needed to make sure that the compiler will not generate a direct invoke to this + // method. Should only be called on non-invokable methods. + void EnsureThrowsInvocationError(ArtMethod* method) + SHARED_REQUIRES(Locks::mutator_lock_); + + // A wrapper class representing the result of a method translation used for linking methods and + // updating superclass default methods. For each method in a classes vtable there are 4 states it + // could be in: + // 1) No translation is necessary. In this case there is no MethodTranslation object for it. This + // is the standard case and is true when the method is not overridable by a default method, + // the class defines a concrete implementation of the method, the default method implementation + // remains the same, or an abstract method stayed abstract. + // 2) The method must be translated to a different default method. We note this with + // CreateTranslatedMethod. + // 3) The method must be replaced with a conflict method. This happens when a superclass + // implements an interface with a default method and this class implements an unrelated + // interface that also defines that default method. We note this with CreateConflictingMethod. + // 4) The method must be replaced with an abstract miranda method. This happens when a superclass + // implements an interface with a default method and this class implements a subinterface of + // the superclass's interface which declares the default method abstract. We note this with + // CreateAbstractMethod. + // + // When a method translation is unnecessary (case #1), we don't put it into the + // default_translation maps. So an instance of MethodTranslation must be in one of #2-#4. + class MethodTranslation { + public: + // This slot must become a default conflict method. + static MethodTranslation CreateConflictingMethod() { + return MethodTranslation(Type::kConflict, /*translation*/nullptr); + } + + // This slot must become an abstract method. + static MethodTranslation CreateAbstractMethod() { + return MethodTranslation(Type::kAbstract, /*translation*/nullptr); + } + + // Use the given method as the current value for this vtable slot during translation. + static MethodTranslation CreateTranslatedMethod(ArtMethod* new_method) { + return MethodTranslation(Type::kTranslation, new_method); + } + + // Returns true if this is a method that must become a conflict method. + bool IsInConflict() const { + return type_ == Type::kConflict; + } + + // Returns true if this is a method that must become an abstract method. + bool IsAbstract() const { + return type_ == Type::kAbstract; + } + + // Returns true if this is a method that must become a different method. + bool IsTranslation() const { + return type_ == Type::kTranslation; + } + + // Get the translated version of this method. + ArtMethod* GetTranslation() const { + DCHECK(IsTranslation()); + DCHECK(translation_ != nullptr); + return translation_; + } + + private: + enum class Type { + kTranslation, + kConflict, + kAbstract, + }; + + MethodTranslation(Type type, ArtMethod* translation) + : translation_(translation), type_(type) {} + + ArtMethod* const translation_; + const Type type_; + }; + // Links the virtual methods for the given class and records any default methods that will need to // be updated later. // @@ -728,9 +821,10 @@ class ClassLinker { // scan, we therefore store the vtable index's that might need to be // updated with the method they will turn into. // TODO This whole default_translations thing is very dirty. There should be a better way. - bool LinkVirtualMethods(Thread* self, - Handle<mirror::Class> klass, - /*out*/std::unordered_map<size_t, ArtMethod*>* default_translations) + bool LinkVirtualMethods( + Thread* self, + Handle<mirror::Class> klass, + /*out*/std::unordered_map<size_t, MethodTranslation>* default_translations) SHARED_REQUIRES(Locks::mutator_lock_); // Sets up the interface lookup table (IFTable) in the correct order to allow searching for @@ -740,6 +834,13 @@ class ClassLinker { Handle<mirror::ObjectArray<mirror::Class>> interfaces) SHARED_REQUIRES(Locks::mutator_lock_); + + enum class DefaultMethodSearchResult { + kDefaultFound, + kAbstractFound, + kDefaultConflict + }; + // Find the default method implementation for 'interface_method' in 'klass', if one exists. // // Arguments: @@ -747,31 +848,31 @@ class ClassLinker { // * target_method - The method we are trying to find a default implementation for. // * klass - The class we are searching for a definition of target_method. // * out_default_method - The pointer we will store the found default method to on success. - // * icce_message - A string we will store an appropriate IncompatibleClassChangeError message - // into in case of failure. Note we must do it this way since we do not know - // whether we can allocate the exception object, which could cause us to go to - // sleep. // // Return value: - // * True - There were no conflicting method implementations found in the class while searching - // for target_method. The default method implementation is stored into out_default_method - // if it was found. Otherwise *out_default_method will be set to nullptr. - // * False - Conflicting method implementations were found when searching for target_method. The - // value of *out_default_method is undefined and *icce_message is a string that should - // be used to create an IncompatibleClassChangeError as soon as possible. - bool FindDefaultMethodImplementation(Thread* self, - ArtMethod* target_method, - Handle<mirror::Class> klass, - /*out*/ArtMethod** out_default_method, - /*out*/std::string* icce_message) const + // * kDefaultFound - There were no conflicting method implementations found in the class while + // searching for target_method. The default method implementation is stored into + // out_default_method. + // * kAbstractFound - There were no conflicting method implementations found in the class while + // searching for target_method but no default implementation was found either. + // out_default_method is set to null and the method should be considered not + // implemented. + // * kDefaultConflict - Conflicting method implementations were found when searching for + // target_method. The value of *out_default_method is null. + DefaultMethodSearchResult FindDefaultMethodImplementation( + Thread* self, + ArtMethod* target_method, + Handle<mirror::Class> klass, + /*out*/ArtMethod** out_default_method) const SHARED_REQUIRES(Locks::mutator_lock_); // Sets the imt entries and fixes up the vtable for the given class by linking all the interface // methods. See LinkVirtualMethods for an explanation of what default_translations is. - bool LinkInterfaceMethods(Thread* self, - Handle<mirror::Class> klass, - const std::unordered_map<size_t, ArtMethod*>& default_translations, - ArtMethod** out_imt) + bool LinkInterfaceMethods( + Thread* self, + Handle<mirror::Class> klass, + const std::unordered_map<size_t, MethodTranslation>& default_translations, + ArtMethod** out_imt) SHARED_REQUIRES(Locks::mutator_lock_); bool LinkStaticFields(Thread* self, Handle<mirror::Class> klass, size_t* class_size) @@ -815,7 +916,7 @@ class ClassLinker { void EnsurePreverifiedMethods(Handle<mirror::Class> c) SHARED_REQUIRES(Locks::mutator_lock_); - mirror::Class* LookupClassFromImage(const char* descriptor) + mirror::Class* LookupClassFromBootImage(const char* descriptor) SHARED_REQUIRES(Locks::mutator_lock_); // Returns null if not found. @@ -879,8 +980,8 @@ class ClassLinker { // New class roots, only used by CMS since the GC needs to mark these in the pause. std::vector<GcRoot<mirror::Class>> new_class_roots_ GUARDED_BY(Locks::classlinker_classes_lock_); - // Do we need to search dex caches to find image classes? - bool dex_cache_image_class_lookup_required_; + // Do we need to search dex caches to find boot image classes? + bool dex_cache_boot_image_class_lookup_required_; // Number of times we've searched dex caches for a class. After a certain number of misses we move // the classes into the class_table_ to avoid dex cache based searches. Atomic<uint32_t> failed_dex_cache_class_lookups_; diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index de692d1368..d68b463950 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -242,6 +242,15 @@ void ThrowIncompatibleClassChangeError(mirror::Class* referrer, const char* fmt, va_end(args); } +void ThrowIncompatibleClassChangeErrorForMethodConflict(ArtMethod* method) { + DCHECK(method != nullptr); + ThrowException("Ljava/lang/IncompatibleClassChangeError;", + /*referrer*/nullptr, + StringPrintf("Conflicting default method implementations %s", + PrettyMethod(method).c_str()).c_str()); +} + + // IOException void ThrowIOException(const char* fmt, ...) { diff --git a/runtime/common_throws.h b/runtime/common_throws.h index 2402e6f7a0..2a0934fb5b 100644 --- a/runtime/common_throws.h +++ b/runtime/common_throws.h @@ -120,6 +120,9 @@ void ThrowIncompatibleClassChangeError(mirror::Class* referrer, const char* fmt, __attribute__((__format__(__printf__, 2, 3))) SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; +void ThrowIncompatibleClassChangeErrorForMethodConflict(ArtMethod* method) + SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; + // IOException void ThrowIOException(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))) diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 3a93aace83..70096f5627 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -231,7 +231,14 @@ std::unique_ptr<const DexFile> DexFile::OpenFile(int fd, const char* location, b return nullptr; } size_t length = sbuf.st_size; - map.reset(MemMap::MapFile(length, PROT_READ, MAP_PRIVATE, fd, 0, location, error_msg)); + map.reset(MemMap::MapFile(length, + PROT_READ, + MAP_PRIVATE, + fd, + 0, + /*low_4gb*/false, + location, + error_msg)); if (map.get() == nullptr) { DCHECK(!error_msg->empty()); return nullptr; diff --git a/runtime/dex_file.h b/runtime/dex_file.h index e7877b2e78..1e44f509f1 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -722,6 +722,7 @@ class DexFile { // const CodeItem* GetCodeItem(const uint32_t code_off) const { + DCHECK_LT(code_off, size_) << "Code item offset larger then maximum allowed offset"; if (code_off == 0) { return nullptr; // native or abstract method } else { diff --git a/runtime/elf_file.cc b/runtime/elf_file.cc index 723ee74eb6..281967054d 100644 --- a/runtime/elf_file.cc +++ b/runtime/elf_file.cc @@ -189,8 +189,14 @@ bool ElfFileImpl<ElfTypes>::Setup(int prot, int flags, std::string* error_msg) { if (program_header_only_) { // first just map ELF header to get program header size information size_t elf_header_size = sizeof(Elf_Ehdr); - if (!SetMap(MemMap::MapFile(elf_header_size, prot, flags, file_->Fd(), 0, - file_->GetPath().c_str(), error_msg), + if (!SetMap(MemMap::MapFile(elf_header_size, + prot, + flags, + file_->Fd(), + 0, + /*low4_gb*/false, + file_->GetPath().c_str(), + error_msg), error_msg)) { return false; } @@ -202,16 +208,28 @@ bool ElfFileImpl<ElfTypes>::Setup(int prot, int flags, std::string* error_msg) { sizeof(Elf_Ehdr), file_->GetPath().c_str()); return false; } - if (!SetMap(MemMap::MapFile(program_header_size, prot, flags, file_->Fd(), 0, - file_->GetPath().c_str(), error_msg), + if (!SetMap(MemMap::MapFile(program_header_size, + prot, + flags, + file_->Fd(), + 0, + /*low4_gb*/false, + file_->GetPath().c_str(), + error_msg), error_msg)) { *error_msg = StringPrintf("Failed to map ELF program headers: %s", error_msg->c_str()); return false; } } else { // otherwise map entire file - if (!SetMap(MemMap::MapFile(file_->GetLength(), prot, flags, file_->Fd(), 0, - file_->GetPath().c_str(), error_msg), + if (!SetMap(MemMap::MapFile(file_->GetLength(), + prot, + flags, + file_->Fd(), + 0, + /*low4_gb*/false, + file_->GetPath().c_str(), + error_msg), error_msg)) { *error_msg = StringPrintf("Failed to map ELF file: %s", error_msg->c_str()); return false; @@ -1258,9 +1276,12 @@ bool ElfFileImpl<ElfTypes>::Load(bool executable, std::string* error_msg) { std::unique_ptr<MemMap> segment( MemMap::MapFileAtAddress(p_vaddr, program_header->p_filesz, - prot, flags, file_->Fd(), + prot, + flags, + file_->Fd(), program_header->p_offset, - true, // implies MAP_FIXED + /*low4_gb*/false, + /*reuse*/true, // implies MAP_FIXED file_->GetPath().c_str(), error_msg)); if (segment.get() == nullptr) { @@ -1775,8 +1796,14 @@ ElfFile* ElfFile::Open(File* file, bool writable, bool program_header_only, std: file->GetPath().c_str()); return nullptr; } - std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT, PROT_READ, MAP_PRIVATE, file->Fd(), 0, - file->GetPath().c_str(), error_msg)); + std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT, + PROT_READ, + MAP_PRIVATE, + file->Fd(), + 0, + /*low4_gb*/false, + file->GetPath().c_str(), + error_msg)); if (map == nullptr && map->Size() != EI_NIDENT) { return nullptr; } @@ -1809,8 +1836,14 @@ ElfFile* ElfFile::Open(File* file, int mmap_prot, int mmap_flags, std::string* e file->GetPath().c_str()); return nullptr; } - std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT, PROT_READ, MAP_PRIVATE, file->Fd(), 0, - file->GetPath().c_str(), error_msg)); + std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT, + PROT_READ, + MAP_PRIVATE, + file->Fd(), + 0, + /*low4_gb*/false, + file->GetPath().c_str(), + error_msg)); if (map == nullptr && map->Size() != EI_NIDENT) { return nullptr; } diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 5eda6d6bd3..abf9ac49e6 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -645,8 +645,8 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // frame. ScopedQuickEntrypointChecks sqec(self); - if (method->IsAbstract()) { - ThrowAbstractMethodError(method); + if (UNLIKELY(!method->IsInvokable())) { + method->ThrowInvocationTimeError(); return 0; } diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc index 1361f7bcc5..8f7bb946c3 100644 --- a/runtime/gc/accounting/mod_union_table.cc +++ b/runtime/gc/accounting/mod_union_table.cc @@ -487,7 +487,7 @@ void ModUnionTableCardCache::ClearCards() { // Mark all references to the alloc space(s). void ModUnionTableCardCache::UpdateAndMarkReferences(MarkObjectVisitor* visitor) { - auto* image_space = heap_->GetImageSpace(); + auto* image_space = heap_->GetBootImageSpace(); // If we don't have an image space, just pass in space_ as the immune space. Pass in the same // space_ instead of image_space to avoid a null check in ModUnionUpdateObjectReferencesVisitor. CardBitVisitor bit_visitor(visitor, space_, image_space != nullptr ? image_space : space_, diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 4a49712cbc..f4cf3ae260 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -23,7 +23,7 @@ #include "gc/accounting/space_bitmap-inl.h" #include "gc/reference_processor.h" #include "gc/space/image_space.h" -#include "gc/space/space.h" +#include "gc/space/space-inl.h" #include "intern_table.h" #include "mirror/class-inl.h" #include "mirror/object-inl.h" @@ -358,13 +358,17 @@ void ConcurrentCopying::MarkingPhase() { // scan the image objects from roots by relying on the card table, // but it's necessary for the RB to-space invariant to hold. TimingLogger::ScopedTiming split1("VisitImageRoots", GetTimings()); - gc::space::ImageSpace* image = heap_->GetImageSpace(); - if (image != nullptr) { - mirror::ObjectArray<mirror::Object>* image_root = image->GetImageHeader().GetImageRoots(); - mirror::Object* marked_image_root = Mark(image_root); - CHECK_EQ(image_root, marked_image_root) << "An image object does not move"; - if (ReadBarrier::kEnableToSpaceInvariantChecks) { - AssertToSpaceInvariant(nullptr, MemberOffset(0), marked_image_root); + for (space::ContinuousSpace* space : heap_->GetContinuousSpaces()) { + if (space->IsImageSpace()) { + gc::space::ImageSpace* image = space->AsImageSpace(); + if (image != nullptr) { + mirror::ObjectArray<mirror::Object>* image_root = image->GetImageHeader().GetImageRoots(); + mirror::Object* marked_image_root = Mark(image_root); + CHECK_EQ(image_root, marked_image_root) << "An image object does not move"; + if (ReadBarrier::kEnableToSpaceInvariantChecks) { + AssertToSpaceInvariant(nullptr, MemberOffset(0), marked_image_root); + } + } } } } diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index ab931429d0..da9a79e1a2 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -233,7 +233,8 @@ Heap::Heap(size_t initial_size, backtrace_lock_(nullptr), seen_backtrace_count_(0u), unique_backtrace_count_(0u), - gc_disabled_for_shutdown_(false) { + gc_disabled_for_shutdown_(false), + boot_image_space_(nullptr) { if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) { LOG(INFO) << "Heap() entering"; } @@ -262,15 +263,16 @@ Heap::Heap(size_t initial_size, if (!image_file_name.empty()) { ATRACE_BEGIN("ImageSpace::Create"); std::string error_msg; - auto* image_space = space::ImageSpace::Create(image_file_name.c_str(), image_instruction_set, + boot_image_space_ = space::ImageSpace::Create(image_file_name.c_str(), + image_instruction_set, &error_msg); ATRACE_END(); - if (image_space != nullptr) { - AddSpace(image_space); + if (boot_image_space_ != nullptr) { + AddSpace(boot_image_space_); // Oat files referenced by image files immediately follow them in memory, ensure alloc space // isn't going to get in the middle - uint8_t* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd(); - CHECK_GT(oat_file_end_addr, image_space->End()); + uint8_t* oat_file_end_addr = boot_image_space_->GetImageHeader().GetOatFileEnd(); + CHECK_GT(oat_file_end_addr, boot_image_space_->End()); requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize); } else { LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. " @@ -454,11 +456,11 @@ Heap::Heap(size_t initial_size, rb_table_.reset(new accounting::ReadBarrierTable()); DCHECK(rb_table_->IsAllCleared()); } - if (GetImageSpace() != nullptr) { + if (GetBootImageSpace() != nullptr) { // Don't add the image mod union table if we are running without an image, this can crash if // we use the CardCache implementation. accounting::ModUnionTable* mod_union_table = new accounting::ModUnionTableToZygoteAllocspace( - "Image mod-union table", this, GetImageSpace()); + "Image mod-union table", this, GetBootImageSpace()); CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table"; AddModUnionTable(mod_union_table); } @@ -523,12 +525,12 @@ Heap::Heap(size_t initial_size, garbage_collectors_.push_back(mark_compact_collector_); } } - if (GetImageSpace() != nullptr && non_moving_space_ != nullptr && + if (GetBootImageSpace() != nullptr && non_moving_space_ != nullptr && (is_zygote || separate_non_moving_space || foreground_collector_type_ == kCollectorTypeGSS)) { // Check that there's no gap between the image space and the non moving space so that the // immune region won't break (eg. due to a large object allocated in the gap). This is only // required when we're the zygote or using GSS. - bool no_gap = MemMap::CheckNoGaps(GetImageSpace()->GetMemMap(), + bool no_gap = MemMap::CheckNoGaps(GetBootImageSpace()->GetMemMap(), non_moving_space_->GetMemMap()); if (!no_gap) { PrintFileToLog("/proc/self/maps", LogSeverity::ERROR); @@ -748,15 +750,6 @@ bool Heap::IsCompilingBoot() const { return true; } -bool Heap::HasImageSpace() const { - for (const auto& space : continuous_spaces_) { - if (space->IsImageSpace()) { - return true; - } - } - return false; -} - void Heap::IncrementDisableMovingGC(Thread* self) { // Need to do this holding the lock to prevent races where the GC is about to run / running when // we attempt to disable it. @@ -1164,6 +1157,7 @@ Heap::~Heap() { STLDeleteElements(&continuous_spaces_); STLDeleteElements(&discontinuous_spaces_); delete gc_complete_lock_; + delete thread_flip_lock_; delete pending_task_lock_; delete backtrace_lock_; if (unique_backtrace_count_.LoadRelaxed() != 0 || seen_backtrace_count_.LoadRelaxed() != 0) { @@ -1208,13 +1202,8 @@ space::Space* Heap::FindSpaceFromObject(const mirror::Object* obj, bool fail_ok) return FindDiscontinuousSpaceFromObject(obj, fail_ok); } -space::ImageSpace* Heap::GetImageSpace() const { - for (const auto& space : continuous_spaces_) { - if (space->IsImageSpace()) { - return space->AsImageSpace(); - } - } - return nullptr; +space::ImageSpace* Heap::GetBootImageSpace() const { + return boot_image_space_; } void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) { diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index cc48172f71..e23b1a3166 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -580,9 +580,9 @@ class Heap { // Unbind any bound bitmaps. void UnBindBitmaps() REQUIRES(Locks::heap_bitmap_lock_); - // DEPRECATED: Should remove in "near" future when support for multiple image spaces is added. - // Assumes there is only one image space. - space::ImageSpace* GetImageSpace() const; + // Returns the boot image space. There may be multiple image spaces, but there is only one boot + // image space. + space::ImageSpace* GetBootImageSpace() const; // Permenantly disable moving garbage collection. void DisableMovingGc() REQUIRES(!*gc_complete_lock_); @@ -660,7 +660,9 @@ class Heap { void RemoveRememberedSet(space::Space* space); bool IsCompilingBoot() const; - bool HasImageSpace() const; + bool HasImageSpace() const { + return boot_image_space_ != nullptr; + } ReferenceProcessor* GetReferenceProcessor() { return reference_processor_.get(); @@ -1320,6 +1322,9 @@ class Heap { // allocating. bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_); + // Boot image space. + space::ImageSpace* boot_image_space_; + friend class CollectorTransitionTask; friend class collector::GarbageCollector; friend class collector::MarkCompact; diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index ce64b10364..1fe9a03159 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -709,19 +709,32 @@ ImageSpace* ImageSpace::Init(const char* image_filename, const char* image_locat } // Note: The image header is part of the image due to mmap page alignment required of offset. - std::unique_ptr<MemMap> map(MemMap::MapFileAtAddress( - image_header.GetImageBegin(), image_header.GetImageSize(), - PROT_READ | PROT_WRITE, MAP_PRIVATE, file->Fd(), 0, false, image_filename, error_msg)); - if (map.get() == nullptr) { + std::unique_ptr<MemMap> map(MemMap::MapFileAtAddress(image_header.GetImageBegin(), + image_header.GetImageSize(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + file->Fd(), + 0, + /*low_4gb*/false, + /*reuse*/false, + image_filename, + error_msg)); + if (map == nullptr) { DCHECK(!error_msg->empty()); return nullptr; } CHECK_EQ(image_header.GetImageBegin(), map->Begin()); DCHECK_EQ(0, memcmp(&image_header, map->Begin(), sizeof(ImageHeader))); - std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress( - nullptr, bitmap_section.Size(), PROT_READ, MAP_PRIVATE, file->Fd(), - bitmap_section.Offset(), false, image_filename, error_msg)); + std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress(nullptr, + bitmap_section.Size(), + PROT_READ, MAP_PRIVATE, + file->Fd(), + bitmap_section.Offset(), + /*low_4gb*/false, + /*reuse*/false, + image_filename, + error_msg)); if (image_map.get() == nullptr) { *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str()); return nullptr; diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 0bc9dd8375..bc2c197d33 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -108,7 +108,7 @@ static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) } void Instrumentation::InstallStubsForMethod(ArtMethod* method) { - if (method->IsAbstract() || method->IsProxyMethod()) { + if (!method->IsInvokable() || method->IsProxyMethod()) { // Do not change stubs for these methods. return; } @@ -734,7 +734,7 @@ bool Instrumentation::IsDeoptimizedMethodsEmpty() const { void Instrumentation::Deoptimize(ArtMethod* method) { CHECK(!method->IsNative()); CHECK(!method->IsProxyMethod()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); Thread* self = Thread::Current(); { @@ -757,7 +757,7 @@ void Instrumentation::Deoptimize(ArtMethod* method) { void Instrumentation::Undeoptimize(ArtMethod* method) { CHECK(!method->IsNative()); CHECK(!method->IsProxyMethod()); - CHECK(!method->IsAbstract()); + CHECK(method->IsInvokable()); Thread* self = Thread::Current(); bool empty; diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc index f4658d5342..e2e4782d00 100644 --- a/runtime/intern_table.cc +++ b/runtime/intern_table.cc @@ -187,7 +187,7 @@ mirror::String* InternTable::LookupStringFromImage(mirror::String* s) { if (image_added_to_intern_table_) { return nullptr; } - gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image = Runtime::Current()->GetHeap()->GetBootImageSpace(); if (image == nullptr) { return nullptr; // No image present. } diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index 7c0594a8bb..d686f749f3 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -262,7 +262,7 @@ static JValue Execute(Thread* self, const DexFile::CodeItem* code_item, ShadowFr static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register) { - DCHECK(!shadow_frame.GetMethod()->IsAbstract()); + DCHECK(shadow_frame.GetMethod()->IsInvokable()); DCHECK(!shadow_frame.GetMethod()->IsNative()); shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self); @@ -318,9 +318,9 @@ void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receive if (code_item != nullptr) { num_regs = code_item->registers_size_; num_ins = code_item->ins_size_; - } else if (method->IsAbstract()) { + } else if (!method->IsInvokable()) { self->EndAssertNoThreadSuspension(old_cause); - ThrowAbstractMethodError(method); + method->ThrowInvocationTimeError(); return; } else { DCHECK(method->IsNative()); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index c8650c4238..9f6699f730 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -141,8 +141,9 @@ static inline bool IsValidLambdaTargetOrThrow(ArtMethod* called_method) if (UNLIKELY(called_method == nullptr)) { // The shadow frame should already be pushed, so we don't need to update it. - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); + // We got an error. // TODO(iam): Also handle the case when the method is non-static, what error do we throw? // TODO(iam): Also make sure that ACC_LAMBDA is set. } else if (UNLIKELY(called_method->GetCodeItem() == nullptr)) { @@ -617,8 +618,8 @@ static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instr CHECK(self->IsExceptionPending()); result->SetJ(0); return false; - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); result->SetJ(0); return false; } else { @@ -656,8 +657,8 @@ static inline bool DoInvokeVirtualQuick(Thread* self, ShadowFrame& shadow_frame, CHECK(self->IsExceptionPending()); result->SetJ(0); return false; - } else if (UNLIKELY(called_method->IsAbstract())) { - ThrowAbstractMethodError(called_method); + } else if (UNLIKELY(!called_method->IsInvokable())) { + called_method->ThrowInvocationTimeError(); result->SetJ(0); return false; } else { diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc index df6936bf01..f1f4a03861 100644 --- a/runtime/jdwp/jdwp_handler.cc +++ b/runtime/jdwp/jdwp_handler.cc @@ -745,6 +745,15 @@ static JdwpError M_Bytecodes(JdwpState*, Request* request, ExpandBuf* reply) return ERR_NONE; } +// Default implementation for IDEs relying on this command. +static JdwpError M_IsObsolete(JdwpState*, Request* request, ExpandBuf* reply) + SHARED_REQUIRES(Locks::mutator_lock_) { + request->ReadRefTypeId(); // unused reference type ID + request->ReadMethodId(); // unused method ID + expandBufAdd1(reply, false); // a method is never obsolete. + return ERR_NONE; +} + /* * Given an object reference, return the runtime type of the object * (class or array). @@ -1477,7 +1486,7 @@ static const JdwpHandlerMap gHandlers[] = { { 6, 1, M_LineTable, "Method.LineTable" }, { 6, 2, M_VariableTable, "Method.VariableTable" }, { 6, 3, M_Bytecodes, "Method.Bytecodes" }, - { 6, 4, nullptr, "Method.IsObsolete" }, + { 6, 4, M_IsObsolete, "Method.IsObsolete" }, { 6, 5, M_VariableTableWithGeneric, "Method.VariableTableWithGeneric" }, /* Field command set (8) */ diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc index 2d3581da8f..4b2ac20111 100644 --- a/runtime/mem_map.cc +++ b/runtime/mem_map.cc @@ -252,9 +252,13 @@ static bool CheckMapRequest(uint8_t* expected_ptr, void* actual_ptr, size_t byte } #if USE_ART_LOW_4G_ALLOCATOR -static inline void* TryMemMapLow4GB(void* ptr, size_t page_aligned_byte_count, int prot, int flags, - int fd) { - void* actual = mmap(ptr, page_aligned_byte_count, prot, flags, fd, 0); +static inline void* TryMemMapLow4GB(void* ptr, + size_t page_aligned_byte_count, + int prot, + int flags, + int fd, + off_t offset) { + void* actual = mmap(ptr, page_aligned_byte_count, prot, flags, fd, offset); if (actual != MAP_FAILED) { // Since we didn't use MAP_FIXED the kernel may have mapped it somewhere not in the low // 4GB. If this is the case, unmap and retry. @@ -267,8 +271,13 @@ static inline void* TryMemMapLow4GB(void* ptr, size_t page_aligned_byte_count, i } #endif -MemMap* MemMap::MapAnonymous(const char* name, uint8_t* expected_ptr, size_t byte_count, int prot, - bool low_4gb, bool reuse, std::string* error_msg) { +MemMap* MemMap::MapAnonymous(const char* name, + uint8_t* expected_ptr, + size_t byte_count, + int prot, + bool low_4gb, + bool reuse, + std::string* error_msg) { #ifndef __LP64__ UNUSED(low_4gb); #endif @@ -317,122 +326,14 @@ MemMap* MemMap::MapAnonymous(const char* name, uint8_t* expected_ptr, size_t byt // We need to store and potentially set an error number for pretty printing of errors int saved_errno = 0; -#ifdef __LP64__ - // When requesting low_4g memory and having an expectation, the requested range should fit into - // 4GB. - if (low_4gb && ( - // Start out of bounds. - (reinterpret_cast<uintptr_t>(expected_ptr) >> 32) != 0 || - // End out of bounds. For simplicity, this will fail for the last page of memory. - (reinterpret_cast<uintptr_t>(expected_ptr + page_aligned_byte_count) >> 32) != 0)) { - *error_msg = StringPrintf("The requested address space (%p, %p) cannot fit in low_4gb", - expected_ptr, expected_ptr + page_aligned_byte_count); - return nullptr; - } -#endif - - // TODO: - // A page allocator would be a useful abstraction here, as - // 1) It is doubtful that MAP_32BIT on x86_64 is doing the right job for us - // 2) The linear scheme, even with simple saving of the last known position, is very crude -#if USE_ART_LOW_4G_ALLOCATOR - // MAP_32BIT only available on x86_64. - void* actual = MAP_FAILED; - if (low_4gb && expected_ptr == nullptr) { - bool first_run = true; - - MutexLock mu(Thread::Current(), *Locks::mem_maps_lock_); - for (uintptr_t ptr = next_mem_pos_; ptr < 4 * GB; ptr += kPageSize) { - // Use maps_ as an optimization to skip over large maps. - // Find the first map which is address > ptr. - auto it = maps_->upper_bound(reinterpret_cast<void*>(ptr)); - if (it != maps_->begin()) { - auto before_it = it; - --before_it; - // Start at the end of the map before the upper bound. - ptr = std::max(ptr, reinterpret_cast<uintptr_t>(before_it->second->BaseEnd())); - CHECK_ALIGNED(ptr, kPageSize); - } - while (it != maps_->end()) { - // How much space do we have until the next map? - size_t delta = reinterpret_cast<uintptr_t>(it->first) - ptr; - // If the space may be sufficient, break out of the loop. - if (delta >= page_aligned_byte_count) { - break; - } - // Otherwise, skip to the end of the map. - ptr = reinterpret_cast<uintptr_t>(it->second->BaseEnd()); - CHECK_ALIGNED(ptr, kPageSize); - ++it; - } - - // Try to see if we get lucky with this address since none of the ART maps overlap. - actual = TryMemMapLow4GB(reinterpret_cast<void*>(ptr), page_aligned_byte_count, prot, flags, - fd.get()); - if (actual != MAP_FAILED) { - next_mem_pos_ = reinterpret_cast<uintptr_t>(actual) + page_aligned_byte_count; - break; - } - - if (4U * GB - ptr < page_aligned_byte_count) { - // Not enough memory until 4GB. - if (first_run) { - // Try another time from the bottom; - ptr = LOW_MEM_START - kPageSize; - first_run = false; - continue; - } else { - // Second try failed. - break; - } - } - - uintptr_t tail_ptr; - - // Check pages are free. - bool safe = true; - for (tail_ptr = ptr; tail_ptr < ptr + page_aligned_byte_count; tail_ptr += kPageSize) { - if (msync(reinterpret_cast<void*>(tail_ptr), kPageSize, 0) == 0) { - safe = false; - break; - } else { - DCHECK_EQ(errno, ENOMEM); - } - } - - next_mem_pos_ = tail_ptr; // update early, as we break out when we found and mapped a region - - if (safe == true) { - actual = TryMemMapLow4GB(reinterpret_cast<void*>(ptr), page_aligned_byte_count, prot, flags, - fd.get()); - if (actual != MAP_FAILED) { - break; - } - } else { - // Skip over last page. - ptr = tail_ptr; - } - } - - if (actual == MAP_FAILED) { - LOG(ERROR) << "Could not find contiguous low-memory space."; - saved_errno = ENOMEM; - } - } else { - actual = mmap(expected_ptr, page_aligned_byte_count, prot, flags, fd.get(), 0); - saved_errno = errno; - } - -#else -#if defined(__LP64__) - if (low_4gb && expected_ptr == nullptr) { - flags |= MAP_32BIT; - } -#endif - - void* actual = mmap(expected_ptr, page_aligned_byte_count, prot, flags, fd.get(), 0); + void* actual = MapInternal(expected_ptr, + page_aligned_byte_count, + prot, + flags, + fd.get(), + 0, + low_4gb); saved_errno = errno; -#endif if (actual == MAP_FAILED) { PrintFileToLog("/proc/self/maps", LogSeverity::WARNING); @@ -458,8 +359,15 @@ MemMap* MemMap::MapDummy(const char* name, uint8_t* addr, size_t byte_count) { return new MemMap(name, addr, byte_count, addr, page_aligned_byte_count, 0, true /* reuse */); } -MemMap* MemMap::MapFileAtAddress(uint8_t* expected_ptr, size_t byte_count, int prot, int flags, - int fd, off_t start, bool reuse, const char* filename, +MemMap* MemMap::MapFileAtAddress(uint8_t* expected_ptr, + size_t byte_count, + int prot, + int flags, + int fd, + off_t start, + bool low_4gb, + bool reuse, + const char* filename, std::string* error_msg) { CHECK_NE(0, prot); CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE)); @@ -498,12 +406,13 @@ MemMap* MemMap::MapFileAtAddress(uint8_t* expected_ptr, size_t byte_count, int p page_aligned_byte_count += redzone_size; } - uint8_t* actual = reinterpret_cast<uint8_t*>(mmap(page_aligned_expected, - page_aligned_byte_count, - prot, - flags, - fd, - page_aligned_offset)); + uint8_t* actual = reinterpret_cast<uint8_t*>(MapInternal(page_aligned_expected, + page_aligned_byte_count, + prot, + flags, + fd, + page_aligned_offset, + low_4gb)); if (actual == MAP_FAILED) { auto saved_errno = errno; @@ -827,6 +736,132 @@ void MemMap::SetSize(size_t new_size) { size_ = new_size; } +void* MemMap::MapInternal(void* addr, + size_t length, + int prot, + int flags, + int fd, + off_t offset, + bool low_4gb) { +#ifdef __LP64__ + // When requesting low_4g memory and having an expectation, the requested range should fit into + // 4GB. + if (low_4gb && ( + // Start out of bounds. + (reinterpret_cast<uintptr_t>(addr) >> 32) != 0 || + // End out of bounds. For simplicity, this will fail for the last page of memory. + ((reinterpret_cast<uintptr_t>(addr) + length) >> 32) != 0)) { + LOG(ERROR) << "The requested address space (" << addr << ", " + << reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + length) + << ") cannot fit in low_4gb"; + return MAP_FAILED; + } +#else + UNUSED(low_4gb); +#endif + DCHECK_ALIGNED(length, kPageSize); + if (low_4gb) { + DCHECK_EQ(flags & MAP_FIXED, 0); + } + // TODO: + // A page allocator would be a useful abstraction here, as + // 1) It is doubtful that MAP_32BIT on x86_64 is doing the right job for us + void* actual = MAP_FAILED; +#if USE_ART_LOW_4G_ALLOCATOR + // MAP_32BIT only available on x86_64. + if (low_4gb && addr == nullptr) { + bool first_run = true; + + MutexLock mu(Thread::Current(), *Locks::mem_maps_lock_); + for (uintptr_t ptr = next_mem_pos_; ptr < 4 * GB; ptr += kPageSize) { + // Use maps_ as an optimization to skip over large maps. + // Find the first map which is address > ptr. + auto it = maps_->upper_bound(reinterpret_cast<void*>(ptr)); + if (it != maps_->begin()) { + auto before_it = it; + --before_it; + // Start at the end of the map before the upper bound. + ptr = std::max(ptr, reinterpret_cast<uintptr_t>(before_it->second->BaseEnd())); + CHECK_ALIGNED(ptr, kPageSize); + } + while (it != maps_->end()) { + // How much space do we have until the next map? + size_t delta = reinterpret_cast<uintptr_t>(it->first) - ptr; + // If the space may be sufficient, break out of the loop. + if (delta >= length) { + break; + } + // Otherwise, skip to the end of the map. + ptr = reinterpret_cast<uintptr_t>(it->second->BaseEnd()); + CHECK_ALIGNED(ptr, kPageSize); + ++it; + } + + // Try to see if we get lucky with this address since none of the ART maps overlap. + actual = TryMemMapLow4GB(reinterpret_cast<void*>(ptr), length, prot, flags, fd, offset); + if (actual != MAP_FAILED) { + next_mem_pos_ = reinterpret_cast<uintptr_t>(actual) + length; + return actual; + } + + if (4U * GB - ptr < length) { + // Not enough memory until 4GB. + if (first_run) { + // Try another time from the bottom; + ptr = LOW_MEM_START - kPageSize; + first_run = false; + continue; + } else { + // Second try failed. + break; + } + } + + uintptr_t tail_ptr; + + // Check pages are free. + bool safe = true; + for (tail_ptr = ptr; tail_ptr < ptr + length; tail_ptr += kPageSize) { + if (msync(reinterpret_cast<void*>(tail_ptr), kPageSize, 0) == 0) { + safe = false; + break; + } else { + DCHECK_EQ(errno, ENOMEM); + } + } + + next_mem_pos_ = tail_ptr; // update early, as we break out when we found and mapped a region + + if (safe == true) { + actual = TryMemMapLow4GB(reinterpret_cast<void*>(ptr), length, prot, flags, fd, offset); + if (actual != MAP_FAILED) { + return actual; + } + } else { + // Skip over last page. + ptr = tail_ptr; + } + } + + if (actual == MAP_FAILED) { + LOG(ERROR) << "Could not find contiguous low-memory space."; + errno = ENOMEM; + } + } else { + actual = mmap(addr, length, prot, flags, fd, offset); + } + +#else +#if defined(__LP64__) + if (low_4gb && addr == nullptr) { + flags |= MAP_32BIT; + } +#endif + actual = mmap(addr, length, prot, flags, fd, offset); +#endif + return actual; +} + std::ostream& operator<<(std::ostream& os, const MemMap& mem_map) { os << StringPrintf("[MemMap: %p-%p prot=0x%x %s]", mem_map.BaseBegin(), mem_map.BaseEnd(), mem_map.GetProtect(), diff --git a/runtime/mem_map.h b/runtime/mem_map.h index 7c11cebcef..a67a925218 100644 --- a/runtime/mem_map.h +++ b/runtime/mem_map.h @@ -61,8 +61,13 @@ class MemMap { // a name. // // On success, returns returns a MemMap instance. On failure, returns null. - static MemMap* MapAnonymous(const char* ashmem_name, uint8_t* addr, size_t byte_count, int prot, - bool low_4gb, bool reuse, std::string* error_msg); + static MemMap* MapAnonymous(const char* ashmem_name, + uint8_t* addr, + size_t byte_count, + int prot, + bool low_4gb, + bool reuse, + std::string* error_msg); // Create placeholder for a region allocated by direct call to mmap. // This is useful when we do not have control over the code calling mmap, @@ -74,10 +79,24 @@ class MemMap { // "start" offset is absolute, not relative. // // On success, returns returns a MemMap instance. On failure, returns null. - static MemMap* MapFile(size_t byte_count, int prot, int flags, int fd, off_t start, - const char* filename, std::string* error_msg) { - return MapFileAtAddress( - nullptr, byte_count, prot, flags, fd, start, false, filename, error_msg); + static MemMap* MapFile(size_t byte_count, + int prot, + int flags, + int fd, + off_t start, + bool low_4gb, + const char* filename, + std::string* error_msg) { + return MapFileAtAddress(nullptr, + byte_count, + prot, + flags, + fd, + start, + /*low_4gb*/low_4gb, + /*reuse*/false, + filename, + error_msg); } // Map part of a file, taking care of non-page aligned offsets. The @@ -87,8 +106,15 @@ class MemMap { // mapping where we do not take ownership of the memory. // // On success, returns returns a MemMap instance. On failure, returns null. - static MemMap* MapFileAtAddress(uint8_t* addr, size_t byte_count, int prot, int flags, int fd, - off_t start, bool reuse, const char* filename, + static MemMap* MapFileAtAddress(uint8_t* addr, + size_t byte_count, + int prot, + int flags, + int fd, + off_t start, + bool low_4gb, + bool reuse, + const char* filename, std::string* error_msg); // Releases the memory mapping. @@ -138,7 +164,9 @@ class MemMap { } // Unmap the pages at end and remap them to create another memory map. - MemMap* RemapAtEnd(uint8_t* new_end, const char* tail_name, int tail_prot, + MemMap* RemapAtEnd(uint8_t* new_end, + const char* tail_name, + int tail_prot, std::string* error_msg); static bool CheckNoGaps(MemMap* begin_map, MemMap* end_map) @@ -152,8 +180,14 @@ class MemMap { static void Shutdown() REQUIRES(!Locks::mem_maps_lock_); private: - MemMap(const std::string& name, uint8_t* begin, size_t size, void* base_begin, size_t base_size, - int prot, bool reuse, size_t redzone_size = 0) REQUIRES(!Locks::mem_maps_lock_); + MemMap(const std::string& name, + uint8_t* begin, + size_t size, + void* base_begin, + size_t base_size, + int prot, + bool reuse, + size_t redzone_size = 0) REQUIRES(!Locks::mem_maps_lock_); static void DumpMapsLocked(std::ostream& os, bool terse) REQUIRES(Locks::mem_maps_lock_); @@ -164,6 +198,15 @@ class MemMap { static bool ContainedWithinExistingMap(uint8_t* ptr, size_t size, std::string* error_msg) REQUIRES(!Locks::mem_maps_lock_); + // Internal version of mmap that supports low 4gb emulation. + static void* MapInternal(void* addr, + size_t length, + int prot, + int flags, + int fd, + off_t offset, + bool low_4gb); + const std::string name_; uint8_t* const begin_; // Start of data. size_t size_; // Length of data. diff --git a/runtime/mem_map_test.cc b/runtime/mem_map_test.cc index 13bf5b7698..edcbcf2dca 100644 --- a/runtime/mem_map_test.cc +++ b/runtime/mem_map_test.cc @@ -18,21 +18,36 @@ #include <memory> +#include "common_runtime_test.h" #include "base/memory_tool.h" - -#include "gtest/gtest.h" +#include "base/unix_file/fd_file.h" namespace art { -class MemMapTest : public testing::Test { +class MemMapTest : public CommonRuntimeTest { public: static uint8_t* BaseBegin(MemMap* mem_map) { return reinterpret_cast<uint8_t*>(mem_map->base_begin_); } + static size_t BaseSize(MemMap* mem_map) { return mem_map->base_size_; } + static uint8_t* GetValidMapAddress(size_t size, bool low_4gb) { + // Find a valid map address and unmap it before returning. + std::string error_msg; + std::unique_ptr<MemMap> map(MemMap::MapAnonymous("temp", + nullptr, + size, + PROT_READ, + low_4gb, + false, + &error_msg)); + CHECK(map != nullptr); + return map->Begin(); + } + static void RemapAtEndTest(bool low_4gb) { std::string error_msg; // Cast the page size to size_t. @@ -164,14 +179,36 @@ TEST_F(MemMapTest, MapAnonymousEmpty32bit) { ASSERT_TRUE(error_msg.empty()); ASSERT_LT(reinterpret_cast<uintptr_t>(BaseBegin(map.get())), 1ULL << 32); } +TEST_F(MemMapTest, MapFile32Bit) { + CommonInit(); + std::string error_msg; + ScratchFile scratch_file; + constexpr size_t kMapSize = kPageSize; + std::unique_ptr<uint8_t[]> data(new uint8_t[kMapSize]()); + ASSERT_TRUE(scratch_file.GetFile()->WriteFully(&data[0], kMapSize)); + std::unique_ptr<MemMap> map(MemMap::MapFile(/*byte_count*/kMapSize, + PROT_READ, + MAP_PRIVATE, + scratch_file.GetFd(), + /*start*/0, + /*low_4gb*/true, + scratch_file.GetFilename().c_str(), + &error_msg)); + ASSERT_TRUE(map != nullptr) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map->Size(), kMapSize); + ASSERT_LT(reinterpret_cast<uintptr_t>(BaseBegin(map.get())), 1ULL << 32); +} #endif TEST_F(MemMapTest, MapAnonymousExactAddr) { CommonInit(); std::string error_msg; + // Find a valid address. + uint8_t* valid_address = GetValidMapAddress(kPageSize, /*low_4gb*/false); // Map at an address that should work, which should succeed. std::unique_ptr<MemMap> map0(MemMap::MapAnonymous("MapAnonymous0", - reinterpret_cast<uint8_t*>(ART_BASE_ADDRESS), + valid_address, kPageSize, PROT_READ | PROT_WRITE, false, @@ -179,7 +216,7 @@ TEST_F(MemMapTest, MapAnonymousExactAddr) { &error_msg)); ASSERT_TRUE(map0.get() != nullptr) << error_msg; ASSERT_TRUE(error_msg.empty()); - ASSERT_TRUE(map0->BaseBegin() == reinterpret_cast<void*>(ART_BASE_ADDRESS)); + ASSERT_TRUE(map0->BaseBegin() == valid_address); // Map at an unspecified address, which should succeed. std::unique_ptr<MemMap> map1(MemMap::MapAnonymous("MapAnonymous1", nullptr, @@ -217,18 +254,27 @@ TEST_F(MemMapTest, MapAnonymousExactAddr32bitHighAddr) { CommonInit(); // This test may not work under valgrind. if (RUNNING_ON_MEMORY_TOOL == 0) { - uintptr_t start_addr = ART_BASE_ADDRESS + 0x1000000; + constexpr size_t size = 0x100000; + // Try all addresses starting from 2GB to 4GB. + size_t start_addr = 2 * GB; std::string error_msg; - std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousExactAddr32bitHighAddr", - reinterpret_cast<uint8_t*>(start_addr), - 0x21000000, - PROT_READ | PROT_WRITE, - true, - false, - &error_msg)); + std::unique_ptr<MemMap> map; + for (; start_addr <= std::numeric_limits<uint32_t>::max() - size; start_addr += size) { + map.reset(MemMap::MapAnonymous("MapAnonymousExactAddr32bitHighAddr", + reinterpret_cast<uint8_t*>(start_addr), + size, + PROT_READ | PROT_WRITE, + /*low_4gb*/true, + false, + &error_msg)); + if (map != nullptr) { + break; + } + } + ASSERT_GE(reinterpret_cast<uintptr_t>(map->End()), 2u * GB); ASSERT_TRUE(map.get() != nullptr) << error_msg; ASSERT_TRUE(error_msg.empty()); - ASSERT_EQ(reinterpret_cast<uintptr_t>(BaseBegin(map.get())), start_addr); + ASSERT_EQ(BaseBegin(map.get()), reinterpret_cast<void*>(start_addr)); } } diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 91e1cecbdf..3590586228 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -726,12 +726,12 @@ ArtField* Class::FindField(Thread* self, Handle<Class> klass, const StringPiece& void Class::SetPreverifiedFlagOnAllMethods(size_t pointer_size) { DCHECK(IsVerified()); for (auto& m : GetDirectMethods(pointer_size)) { - if (!m.IsNative() && !m.IsAbstract()) { + if (!m.IsNative() && m.IsInvokable()) { m.SetPreverified(); } } for (auto& m : GetVirtualMethods(pointer_size)) { - if (!m.IsNative() && !m.IsAbstract()) { + if (!m.IsNative() && m.IsInvokable()) { m.SetPreverified(); } } diff --git a/runtime/modifiers.h b/runtime/modifiers.h index 116cbe9254..9946eabc82 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -50,6 +50,10 @@ static constexpr uint32_t kAccPreverified = 0x00080000; // class (runt static constexpr uint32_t kAccFastNative = 0x00080000; // method (dex only) static constexpr uint32_t kAccMiranda = 0x00200000; // method (dex only) static constexpr uint32_t kAccDefault = 0x00400000; // method (runtime) +// This is set by the class linker during LinkInterfaceMethods. Prior to that point we do not know +// if any particular method needs to be a default conflict. Used to figure out at runtime if +// invoking this method will throw an exception. +static constexpr uint32_t kAccDefaultConflict = 0x00800000; // method (runtime) // Special runtime-only flags. // Interface and all its super-interfaces with default methods have been recursively initialized. diff --git a/runtime/monitor.cc b/runtime/monitor.cc index da21fee3d2..19c71f6d97 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -485,10 +485,7 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, DCHECK(why == kTimedWaiting || why == kSleeping) << why; self->GetWaitConditionVariable()->TimedWait(self, ms, ns); } - if (self->IsInterruptedLocked()) { - was_interrupted = true; - } - self->SetInterruptedLocked(false); + was_interrupted = self->IsInterruptedLocked(); } } @@ -522,7 +519,7 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, monitor_lock_.Unlock(self); - if (was_interrupted) { + if (was_interrupted && interruptShouldThrow) { /* * We were interrupted while waiting, or somebody interrupted an * un-interruptible thread earlier and we're bailing out immediately. @@ -534,9 +531,7 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, MutexLock mu(self, *self->GetWaitMutex()); self->SetInterruptedLocked(false); } - if (interruptShouldThrow) { - self->ThrowNewException("Ljava/lang/InterruptedException;", nullptr); - } + self->ThrowNewException("Ljava/lang/InterruptedException;", nullptr); } } diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 99080f611c..0f3a013d53 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -846,7 +846,7 @@ std::string OatFileAssistant::OldProfileFileName() { std::string OatFileAssistant::ImageLocation() { Runtime* runtime = Runtime::Current(); - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); + const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace(); if (image_space == nullptr) { return ""; } @@ -949,21 +949,23 @@ const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() { image_info_load_attempted_ = true; Runtime* runtime = Runtime::Current(); - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); + const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace(); if (image_space != nullptr) { cached_image_info_.location = image_space->GetImageLocation(); if (isa_ == kRuntimeISA) { const ImageHeader& image_header = image_space->GetImageHeader(); cached_image_info_.oat_checksum = image_header.GetOatChecksum(); - cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()); + cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>( + image_header.GetOatDataBegin()); cached_image_info_.patch_delta = image_header.GetPatchDelta(); } else { std::unique_ptr<ImageHeader> image_header( gc::space::ImageSpace::ReadImageHeaderOrDie( cached_image_info_.location.c_str(), isa_)); cached_image_info_.oat_checksum = image_header->GetOatChecksum(); - cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()); + cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>( + image_header->GetOatDataBegin()); cached_image_info_.patch_delta = image_header->GetPatchDelta(); } } diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index c54d7f8761..8c7efb2ea8 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -223,7 +223,7 @@ class OatFileAssistantTest : public CommonRuntimeTest { false, dex_location.c_str(), &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); + const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetBootImageSpace(); ASSERT_TRUE(image_space != nullptr); const ImageHeader& image_header = image_space->GetImageHeader(); const OatHeader& oat_header = odex_file->GetOatHeader(); diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 9eee156bb0..ea6d3ff3dd 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -79,11 +79,8 @@ const OatFile* OatFileManager::FindOpenedOatFileFromOatLocationLocked( } const OatFile* OatFileManager::GetBootOatFile() const { - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); - if (image_space == nullptr) { - return nullptr; - } - return image_space->GetOatFile(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); + return (image_space == nullptr) ? nullptr : image_space->GetOatFile(); } const OatFile* OatFileManager::GetPrimaryOatFile() const { diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 556ba56532..17f34c0e21 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -528,13 +528,13 @@ bool Runtime::Start() { // Use !IsAotCompiler so that we get test coverage, tests are never the zygote. if (!IsAotCompiler()) { ScopedObjectAccess soa(self); - gc::space::ImageSpace* image_space = heap_->GetImageSpace(); + gc::space::ImageSpace* image_space = heap_->GetBootImageSpace(); if (image_space != nullptr) { ATRACE_BEGIN("AddImageStringsToTable"); GetInternTable()->AddImageStringsToTable(image_space); ATRACE_END(); ATRACE_BEGIN("MoveImageClassesToClassTable"); - GetClassLinker()->MoveImageClassesToClassTable(); + GetClassLinker()->AddBootImageClassesToClassTable(); ATRACE_END(); } } @@ -930,7 +930,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs)); ATRACE_END(); - if (heap_->GetImageSpace() == nullptr && !allow_dex_file_fallback_) { + if (heap_->GetBootImageSpace() == nullptr && !allow_dex_file_fallback_) { LOG(ERROR) << "Dex file fallback disabled, cannot continue without image."; ATRACE_END(); return false; @@ -1043,7 +1043,7 @@ bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) class_linker_->InitFromImage(); ATRACE_END(); if (kIsDebugBuild) { - GetHeap()->GetImageSpace()->VerifyImageAllocations(); + GetHeap()->GetBootImageSpace()->VerifyImageAllocations(); } if (boot_class_path_string_.empty()) { // The bootclasspath is not explicitly specified: construct it from the loaded dex files. diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index aae031798e..364b8cefbc 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -52,7 +52,7 @@ namespace art { namespace verifier { static constexpr bool kTimeVerifyMethod = !kIsDebugBuild; -static constexpr bool gDebugVerify = false; +static constexpr bool kDebugVerify = false; // TODO: Add a constant to method_verifier to turn on verbose logging? // On VLOG(verifier), should we dump the whole state when we run into a hard failure? @@ -114,21 +114,10 @@ static void SafelyMarkAllRegistersAsConflicts(MethodVerifier* verifier, Register reg_line->MarkAllRegistersAsConflicts(verifier); } -MethodVerifier::FailureKind MethodVerifier::VerifyMethod( - ArtMethod* method, bool allow_soft_failures, std::string* error ATTRIBUTE_UNUSED) { - StackHandleScope<2> hs(Thread::Current()); - mirror::Class* klass = method->GetDeclaringClass(); - auto h_dex_cache(hs.NewHandle(klass->GetDexCache())); - auto h_class_loader(hs.NewHandle(klass->GetClassLoader())); - return VerifyMethod(hs.Self(), method->GetDexMethodIndex(), method->GetDexFile(), h_dex_cache, - h_class_loader, klass->GetClassDef(), method->GetCodeItem(), method, - method->GetAccessFlags(), allow_soft_failures, false); -} - - MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self, mirror::Class* klass, bool allow_soft_failures, + bool log_hard_failures, std::string* error) { if (klass->IsVerified()) { return kNoFailure; @@ -160,51 +149,49 @@ MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self, StackHandleScope<2> hs(self); Handle<mirror::DexCache> dex_cache(hs.NewHandle(klass->GetDexCache())); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(klass->GetClassLoader())); - return VerifyClass( - self, &dex_file, dex_cache, class_loader, class_def, allow_soft_failures, error); + return VerifyClass(self, + &dex_file, + dex_cache, + class_loader, + class_def, + allow_soft_failures, + log_hard_failures, + error); } -MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self, - const DexFile* dex_file, - Handle<mirror::DexCache> dex_cache, - Handle<mirror::ClassLoader> class_loader, - const DexFile::ClassDef* class_def, - bool allow_soft_failures, - std::string* error) { - DCHECK(class_def != nullptr); - - // A class must not be abstract and final. - if ((class_def->access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) { - *error = "Verifier rejected class "; - *error += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def)); - *error += ": class is abstract and final."; - return kHardFailure; - } +template <bool kDirect> +static bool HasNextMethod(ClassDataItemIterator* it) { + return kDirect ? it->HasNextDirectMethod() : it->HasNextVirtualMethod(); +} - const uint8_t* class_data = dex_file->GetClassData(*class_def); - if (class_data == nullptr) { - // empty class, probably a marker interface - return kNoFailure; - } - ClassDataItemIterator it(*dex_file, class_data); - while (it.HasNextStaticField() || it.HasNextInstanceField()) { - it.Next(); - } - size_t error_count = 0; - bool hard_fail = false; - ClassLinker* linker = Runtime::Current()->GetClassLinker(); - int64_t previous_direct_method_idx = -1; - while (it.HasNextDirectMethod()) { +template <bool kDirect> +void MethodVerifier::VerifyMethods(Thread* self, + ClassLinker* linker, + const DexFile* dex_file, + const DexFile::ClassDef* class_def, + ClassDataItemIterator* it, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + bool allow_soft_failures, + bool log_hard_failures, + bool need_precise_constants, + bool* hard_fail, + size_t* error_count, + std::string* error_string) { + DCHECK(it != nullptr); + + int64_t previous_method_idx = -1; + while (HasNextMethod<kDirect>(it)) { self->AllowThreadSuspension(); - uint32_t method_idx = it.GetMemberIndex(); - if (method_idx == previous_direct_method_idx) { + uint32_t method_idx = it->GetMemberIndex(); + if (method_idx == previous_method_idx) { // smali can create dex files with two encoded_methods sharing the same method_idx // http://code.google.com/p/smali/issues/detail?id=119 - it.Next(); + it->Next(); continue; } - previous_direct_method_idx = method_idx; - InvokeType type = it.GetMethodInvokeType(*class_def); + previous_method_idx = method_idx; + InvokeType type = it->GetMethodInvokeType(*class_def); ArtMethod* method = linker->ResolveMethod( *dex_file, method_idx, dex_cache, class_loader, nullptr, type); if (method == nullptr) { @@ -215,72 +202,99 @@ MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self, DCHECK(method->GetDeclaringClassUnchecked() != nullptr) << type; } StackHandleScope<1> hs(self); + std::string hard_failure_msg; MethodVerifier::FailureKind result = VerifyMethod(self, method_idx, dex_file, dex_cache, class_loader, class_def, - it.GetMethodCodeItem(), - method, it.GetMethodAccessFlags(), allow_soft_failures, false); + it->GetMethodCodeItem(), + method, + it->GetMethodAccessFlags(), + allow_soft_failures, + log_hard_failures, + need_precise_constants, + &hard_failure_msg); if (result != kNoFailure) { if (result == kHardFailure) { - hard_fail = true; - if (error_count > 0) { - *error += "\n"; + if (*error_count > 0) { + *error_string += "\n"; } - *error = "Verifier rejected class "; - *error += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def)); - *error += " due to bad method "; - *error += PrettyMethod(method_idx, *dex_file); - } - ++error_count; - } - it.Next(); - } - int64_t previous_virtual_method_idx = -1; - while (it.HasNextVirtualMethod()) { - self->AllowThreadSuspension(); - uint32_t method_idx = it.GetMemberIndex(); - if (method_idx == previous_virtual_method_idx) { - // smali can create dex files with two encoded_methods sharing the same method_idx - // http://code.google.com/p/smali/issues/detail?id=119 - it.Next(); - continue; - } - previous_virtual_method_idx = method_idx; - InvokeType type = it.GetMethodInvokeType(*class_def); - ArtMethod* method = linker->ResolveMethod( - *dex_file, method_idx, dex_cache, class_loader, nullptr, type); - if (method == nullptr) { - DCHECK(self->IsExceptionPending()); - // We couldn't resolve the method, but continue regardless. - self->ClearException(); - } - StackHandleScope<1> hs(self); - MethodVerifier::FailureKind result = VerifyMethod(self, - method_idx, - dex_file, - dex_cache, - class_loader, - class_def, - it.GetMethodCodeItem(), - method, it.GetMethodAccessFlags(), allow_soft_failures, false); - if (result != kNoFailure) { - if (result == kHardFailure) { - hard_fail = true; - if (error_count > 0) { - *error += "\n"; + if (!*hard_fail) { + *error_string += "Verifier rejected class "; + *error_string += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def)); + *error_string += ":"; } - *error = "Verifier rejected class "; - *error += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def)); - *error += " due to bad method "; - *error += PrettyMethod(method_idx, *dex_file); + *error_string += " "; + *error_string += hard_failure_msg; + *hard_fail = true; } - ++error_count; + *error_count = *error_count + 1; } + it->Next(); + } +} + +MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self, + const DexFile* dex_file, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + const DexFile::ClassDef* class_def, + bool allow_soft_failures, + bool log_hard_failures, + std::string* error) { + DCHECK(class_def != nullptr); + + // A class must not be abstract and final. + if ((class_def->access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) { + *error = "Verifier rejected class "; + *error += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def)); + *error += ": class is abstract and final."; + return kHardFailure; + } + + const uint8_t* class_data = dex_file->GetClassData(*class_def); + if (class_data == nullptr) { + // empty class, probably a marker interface + return kNoFailure; + } + ClassDataItemIterator it(*dex_file, class_data); + while (it.HasNextStaticField() || it.HasNextInstanceField()) { it.Next(); } + size_t error_count = 0; + bool hard_fail = false; + ClassLinker* linker = Runtime::Current()->GetClassLinker(); + // Direct methods. + VerifyMethods<true>(self, + linker, + dex_file, + class_def, + &it, + dex_cache, + class_loader, + allow_soft_failures, + log_hard_failures, + false /* need precise constants */, + &hard_fail, + &error_count, + error); + // Virtual methods. + VerifyMethods<false>(self, + linker, + dex_file, + class_def, + &it, + dex_cache, + class_loader, + allow_soft_failures, + log_hard_failures, + false /* need precise constants */, + &hard_fail, + &error_count, + error); + if (error_count == 0) { return kNoFailure; } else { @@ -299,7 +313,8 @@ static bool IsLargeMethod(const DexFile::CodeItem* const code_item) { return registers_size * insns_size > 4*1024*1024; } -MethodVerifier::FailureKind MethodVerifier::VerifyMethod(Thread* self, uint32_t method_idx, +MethodVerifier::FailureKind MethodVerifier::VerifyMethod(Thread* self, + uint32_t method_idx, const DexFile* dex_file, Handle<mirror::DexCache> dex_cache, Handle<mirror::ClassLoader> class_loader, @@ -308,7 +323,9 @@ MethodVerifier::FailureKind MethodVerifier::VerifyMethod(Thread* self, uint32_t ArtMethod* method, uint32_t method_access_flags, bool allow_soft_failures, - bool need_precise_constants) { + bool log_hard_failures, + bool need_precise_constants, + std::string* hard_failure_msg) { MethodVerifier::FailureKind result = kNoFailure; uint64_t start_ns = kTimeVerifyMethod ? NanoTime() : 0; @@ -321,8 +338,8 @@ MethodVerifier::FailureKind MethodVerifier::VerifyMethod(Thread* self, uint32_t CHECK(!verifier.have_pending_hard_failure_); if (verifier.failures_.size() != 0) { if (VLOG_IS_ON(verifier)) { - verifier.DumpFailures(VLOG_STREAM(verifier) << "Soft verification failures in " - << PrettyMethod(method_idx, *dex_file) << "\n"); + verifier.DumpFailures(VLOG_STREAM(verifier) << "Soft verification failures in " + << PrettyMethod(method_idx, *dex_file) << "\n"); } result = kSoftFailure; } @@ -336,11 +353,18 @@ MethodVerifier::FailureKind MethodVerifier::VerifyMethod(Thread* self, uint32_t result = kSoftFailure; } else { CHECK(verifier.have_pending_hard_failure_); - verifier.DumpFailures(LOG(INFO) << "Verification error in " - << PrettyMethod(method_idx, *dex_file) << "\n"); + if (VLOG_IS_ON(verifier) || log_hard_failures) { + verifier.DumpFailures(LOG(INFO) << "Verification error in " + << PrettyMethod(method_idx, *dex_file) << "\n"); + } + if (hard_failure_msg != nullptr) { + CHECK(!verifier.failure_messages_.empty()); + *hard_failure_msg = + verifier.failure_messages_[verifier.failure_messages_.size() - 1]->str(); + } result = kHardFailure; } - if (gDebugVerify) { + if (kDebugVerify) { std::cout << "\n" << verifier.info_messages_.str(); verifier.Dump(std::cout); } @@ -1741,7 +1765,7 @@ bool MethodVerifier::CodeFlowVerifyMethod() { GetInstructionFlags(insn_idx).ClearChanged(); } - if (gDebugVerify) { + if (kDebugVerify) { /* * Scan for dead code. There's nothing "evil" about dead code * (besides the wasted space), but it indicates a flaw somewhere @@ -1874,7 +1898,7 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { int32_t branch_target = 0; bool just_set_result = false; - if (gDebugVerify) { + if (kDebugVerify) { // Generate processing back trace to debug verifier LogVerifyInfo() << "Processing " << inst->DumpString(dex_file_) << "\n" << work_line_->Dump(this) << "\n"; @@ -4663,7 +4687,7 @@ bool MethodVerifier::UpdateRegisters(uint32_t next_insn, RegisterLine* merge_lin } } else { ArenaUniquePtr<RegisterLine> copy; - if (gDebugVerify) { + if (kDebugVerify) { copy.reset(RegisterLine::Create(target_line->NumRegs(), this)); copy->CopyFromLine(target_line); } @@ -4671,7 +4695,7 @@ bool MethodVerifier::UpdateRegisters(uint32_t next_insn, RegisterLine* merge_lin if (have_pending_hard_failure_) { return false; } - if (gDebugVerify && changed) { + if (kDebugVerify && changed) { LogVerifyInfo() << "Merging at [" << reinterpret_cast<void*>(work_insn_idx_) << "]" << " to [" << reinterpret_cast<void*>(next_insn) << "]: " << "\n" << copy->Dump(this) << " MERGE\n" diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index 7b51d6eea0..719f0d7d40 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -139,14 +139,20 @@ class MethodVerifier { }; /* Verify a class. Returns "kNoFailure" on success. */ - static FailureKind VerifyClass(Thread* self, mirror::Class* klass, bool allow_soft_failures, + static FailureKind VerifyClass(Thread* self, + mirror::Class* klass, + bool allow_soft_failures, + bool log_hard_failures, std::string* error) SHARED_REQUIRES(Locks::mutator_lock_); - static FailureKind VerifyClass(Thread* self, const DexFile* dex_file, + static FailureKind VerifyClass(Thread* self, + const DexFile* dex_file, Handle<mirror::DexCache> dex_cache, Handle<mirror::ClassLoader> class_loader, const DexFile::ClassDef* class_def, - bool allow_soft_failures, std::string* error) + bool allow_soft_failures, + bool log_hard_failures, + std::string* error) SHARED_REQUIRES(Locks::mutator_lock_); static MethodVerifier* VerifyMethodAndDump(Thread* self, @@ -160,9 +166,6 @@ class MethodVerifier { uint32_t method_access_flags) SHARED_REQUIRES(Locks::mutator_lock_); - static FailureKind VerifyMethod(ArtMethod* method, bool allow_soft_failures, - std::string* error) SHARED_REQUIRES(Locks::mutator_lock_); - uint8_t EncodePcToReferenceMapData() const; uint32_t DexFileVersion() const { @@ -310,6 +313,24 @@ class MethodVerifier { // Adds the given string to the end of the last failure message. void AppendToLastFailMessage(std::string); + // Verify all direct or virtual methods of a class. The method assumes that the iterator is + // positioned correctly, and the iterator will be updated. + template <bool kDirect> + static void VerifyMethods(Thread* self, + ClassLinker* linker, + const DexFile* dex_file, + const DexFile::ClassDef* class_def, + ClassDataItemIterator* it, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + bool allow_soft_failures, + bool log_hard_failures, + bool need_precise_constants, + bool* hard_fail, + size_t* error_count, + std::string* error_string) + SHARED_REQUIRES(Locks::mutator_lock_); + /* * Perform verification on a single method. * @@ -321,13 +342,18 @@ class MethodVerifier { * (3) Iterate through the method, checking type safety and looking * for code flow problems. */ - static FailureKind VerifyMethod(Thread* self, uint32_t method_idx, const DexFile* dex_file, + static FailureKind VerifyMethod(Thread* self, uint32_t method_idx, + const DexFile* dex_file, Handle<mirror::DexCache> dex_cache, Handle<mirror::ClassLoader> class_loader, const DexFile::ClassDef* class_def_idx, const DexFile::CodeItem* code_item, - ArtMethod* method, uint32_t method_access_flags, - bool allow_soft_failures, bool need_precise_constants) + ArtMethod* method, + uint32_t method_access_flags, + bool allow_soft_failures, + bool log_hard_failures, + bool need_precise_constants, + std::string* hard_failure_msg) SHARED_REQUIRES(Locks::mutator_lock_); void FindLocksAtDexPc() SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/verifier/method_verifier_test.cc b/runtime/verifier/method_verifier_test.cc index 2ab6b4aaab..c4123d5f52 100644 --- a/runtime/verifier/method_verifier_test.cc +++ b/runtime/verifier/method_verifier_test.cc @@ -37,7 +37,7 @@ class MethodVerifierTest : public CommonRuntimeTest { // Verify the class std::string error_msg; - ASSERT_TRUE(MethodVerifier::VerifyClass(self, klass, true, &error_msg) == MethodVerifier::kNoFailure) + ASSERT_TRUE(MethodVerifier::VerifyClass(self, klass, true, true, &error_msg) == MethodVerifier::kNoFailure) << error_msg; } diff --git a/test/117-nopatchoat/nopatchoat.cc b/test/117-nopatchoat/nopatchoat.cc index 3e533ad62e..b6b1c43589 100644 --- a/test/117-nopatchoat/nopatchoat.cc +++ b/test/117-nopatchoat/nopatchoat.cc @@ -35,7 +35,7 @@ class NoPatchoatTest { } static bool isRelocationDeltaZero() { - gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetBootImageSpace(); return space != nullptr && space->GetImageHeader().GetPatchDelta() == 0; } diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc index 78f884266a..7762b2d2e3 100644 --- a/test/137-cfi/cfi.cc +++ b/test/137-cfi/cfi.cc @@ -92,7 +92,7 @@ static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) { // detecting this. #if __linux__ static bool IsPicImage() { - gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace(); + gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetBootImageSpace(); CHECK(image_space != nullptr); // We should be running with an image. const OatFile* oat_file = image_space->GetOatFile(); CHECK(oat_file != nullptr); // We should have an oat file to go with the image. diff --git a/test/442-checker-constant-folding/src/Main.java b/test/442-checker-constant-folding/src/Main.java index 59e7282ac7..43bc9d06a2 100644 --- a/test/442-checker-constant-folding/src/Main.java +++ b/test/442-checker-constant-folding/src/Main.java @@ -659,6 +659,33 @@ public class Main { /** + * Exercise constant folding on constant (static) condition for null references. + */ + + /// CHECK-START: int Main.StaticConditionNulls() constant_folding_after_inlining (before) + /// CHECK-DAG: <<Null:l\d+>> NullConstant + /// CHECK-DAG: <<Cond:z\d+>> NotEqual [<<Null>>,<<Null>>] + /// CHECK-DAG: If [<<Cond>>] + + /// CHECK-START: int Main.StaticConditionNulls() constant_folding_after_inlining (after) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: If [<<Const0>>] + + /// CHECK-START: int Main.StaticConditionNulls() constant_folding_after_inlining (after) + /// CHECK-NOT: NotEqual + + private static Object getNull() { + return null; + } + + public static int StaticConditionNulls() { + Object a = getNull(); + Object b = getNull(); + return (a == b) ? 5 : 2; + } + + + /** * Exercise constant folding on a program with condition * (i.e. jumps) leading to the creation of many blocks. * @@ -1208,6 +1235,7 @@ public class Main { assertLongEquals(9, XorLongInt()); assertIntEquals(5, StaticCondition()); + assertIntEquals(5, StaticConditionNulls()); assertIntEquals(7, JumpsAndConditionals(true)); assertIntEquals(3, JumpsAndConditionals(false)); diff --git a/test/450-checker-types/src/Main.java b/test/450-checker-types/src/Main.java index f1885def13..accf70ba38 100644 --- a/test/450-checker-types/src/Main.java +++ b/test/450-checker-types/src/Main.java @@ -454,7 +454,7 @@ public class Main { /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:SubclassC exact:false /// CHECK-NEXT: Return [<<Invoke>>] - /// CHECK-START: SubclassC Main.inlineGenerics() reference_type_propagation_after_inlining (after) + /// CHECK-START: SubclassC Main.inlineGenerics() inliner (after) /// CHECK: <<BoundType:l\d+>> BoundType klass:SubclassC exact:false /// CHECK: Return [<<BoundType>>] private SubclassC inlineGenerics() { @@ -466,7 +466,7 @@ public class Main { /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:Final exact:true /// CHECK-NEXT: Return [<<Invoke>>] - /// CHECK-START: Final Main.inlineGenericsFinal() reference_type_propagation_after_inlining (after) + /// CHECK-START: Final Main.inlineGenericsFinal() inliner (after) /// CHECK: <<BoundType:l\d+>> BoundType klass:Final exact:true /// CHECK: Return [<<BoundType>>] private Final inlineGenericsFinal() { @@ -474,7 +474,7 @@ public class Main { return f; } - /// CHECK-START: void Main.boundOnlyOnceIfNotNull(java.lang.Object) reference_type_propagation_after_inlining (after) + /// CHECK-START: void Main.boundOnlyOnceIfNotNull(java.lang.Object) inliner (after) /// CHECK: BoundType /// CHECK-NOT: BoundType private void boundOnlyOnceIfNotNull(Object o) { @@ -483,7 +483,7 @@ public class Main { } } - /// CHECK-START: void Main.boundOnlyOnceIfInstanceOf(java.lang.Object) reference_type_propagation_after_inlining (after) + /// CHECK-START: void Main.boundOnlyOnceIfInstanceOf(java.lang.Object) inliner (after) /// CHECK: BoundType /// CHECK-NOT: BoundType private void boundOnlyOnceIfInstanceOf(Object o) { @@ -492,7 +492,7 @@ public class Main { } } - /// CHECK-START: Final Main.boundOnlyOnceCheckCast(Generic) reference_type_propagation_after_inlining (after) + /// CHECK-START: Final Main.boundOnlyOnceCheckCast(Generic) inliner (after) /// CHECK: BoundType /// CHECK-NOT: BoundType private Final boundOnlyOnceCheckCast(Generic<Final> o) { @@ -508,7 +508,7 @@ public class Main { /// CHECK: <<Phi:l\d+>> Phi klass:Super /// CHECK: NullCheck [<<Phi>>] klass:Super - /// CHECK-START: void Main.updateNodesInTheSameBlockAsPhi(boolean) reference_type_propagation_after_inlining (after) + /// CHECK-START: void Main.updateNodesInTheSameBlockAsPhi(boolean) inliner (after) /// CHECK: <<Phi:l\d+>> Phi klass:SubclassA /// CHECK: NullCheck [<<Phi>>] klass:SubclassA private void updateNodesInTheSameBlockAsPhi(boolean cond) { @@ -519,7 +519,7 @@ public class Main { s.$noinline$f(); } - /// CHECK-START: java.lang.String Main.checkcastPreserveNullCheck(java.lang.Object) reference_type_propagation_after_inlining (after) + /// CHECK-START: java.lang.String Main.checkcastPreserveNullCheck(java.lang.Object) inliner (after) /// CHECK: <<This:l\d+>> ParameterValue /// CHECK: <<Param:l\d+>> ParameterValue /// CHECK: <<Clazz:l\d+>> LoadClass @@ -548,6 +548,51 @@ public class Main { private void argumentCheck(Super s, double d, SubclassA a, Final f) { } + /// CHECK-START: Main Main.getMain(boolean) reference_type_propagation (after) + /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object + /// CHECK: Return [<<Phi>>] + private Main getMain(boolean cond) { + return cond ? null : new Main(); + } + + private Main getNull() { + return null; + } + + private int mainField = 0; + + /// CHECK-START: void Main.testInlinerWidensReturnType(boolean) inliner (before) + /// CHECK: <<Int:i\d+>> IntConstant 0 + /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:Main + /// CHECK: <<NullCheck:l\d+>> NullCheck [<<Invoke>>] klass:Main exact:false + /// CHECK: InstanceFieldSet [<<NullCheck>>,<<Int>>] + + /// CHECK-START: void Main.testInlinerWidensReturnType(boolean) inliner (after) + /// CHECK: <<Int:i\d+>> IntConstant 0 + /// CHECK: <<Phi:l\d+>> Phi klass:java.lang.Object + /// CHECK: <<NullCheck:l\d+>> NullCheck [<<Phi>>] klass:Main exact:false + /// CHECK: InstanceFieldSet [<<NullCheck>>,<<Int>>] + private void testInlinerWidensReturnType(boolean cond) { + Main o = getMain(cond); + o.mainField = 0; + } + + /// CHECK-START: void Main.testInlinerReturnsNull() inliner (before) + /// CHECK: <<Int:i\d+>> IntConstant 0 + /// CHECK: <<Invoke:l\d+>> InvokeStaticOrDirect klass:Main + /// CHECK: <<NullCheck:l\d+>> NullCheck [<<Invoke>>] klass:Main exact:false + /// CHECK: InstanceFieldSet [<<NullCheck>>,<<Int>>] + + /// CHECK-START: void Main.testInlinerReturnsNull() inliner (after) + /// CHECK: <<Int:i\d+>> IntConstant 0 + /// CHECK: <<Null:l\d+>> NullConstant klass:java.lang.Object + /// CHECK: <<NullCheck:l\d+>> NullCheck [<<Null>>] klass:Main exact:false + /// CHECK: InstanceFieldSet [<<NullCheck>>,<<Int>>] + private void testInlinerReturnsNull() { + Main o = getNull(); + o.mainField = 0; + } + public static void main(String[] args) { } } diff --git a/test/530-checker-regression-reftype-final/smali/TestCase.smali b/test/530-checker-regression-reftype-final/smali/TestCase.smali index 8fd7bb7edb..44facfca05 100644 --- a/test/530-checker-regression-reftype-final/smali/TestCase.smali +++ b/test/530-checker-regression-reftype-final/smali/TestCase.smali @@ -23,7 +23,7 @@ # inline any methods from array classes, this bug cannot be triggered and we # verify it using Checker. -## CHECK-START: void TestCase.testInliner() reference_type_propagation_after_inlining (before) +## CHECK-START: void TestCase.testInliner() inliner (after) ## CHECK-DAG: CheckCast [<<Phi:l\d+>>,{{l\d+}}] ## CHECK-DAG: <<Phi>> Phi klass:java.lang.Object[] exact:false diff --git a/test/538-checker-embed-constants/src/Main.java b/test/538-checker-embed-constants/src/Main.java index 979c4c86c0..12f0380df0 100644 --- a/test/538-checker-embed-constants/src/Main.java +++ b/test/538-checker-embed-constants/src/Main.java @@ -260,6 +260,179 @@ public class Main { return arg ^ 0xf00000000000000fL; } + /// CHECK-START-ARM: long Main.shl2(long) disassembly (after) + /// CHECK: lsl{{s?|.w}} <<oh:r\d+>>, {{r\d+}}, #2 + /// CHECK: orr.w <<oh>>, <<oh>>, <<low:r\d+>>, lsr #30 + /// CHECK-DAG: lsl{{s?|.w}} {{r\d+}}, <<low>>, #2 + + /// CHECK-START-ARM: long Main.shl2(long) disassembly (after) + /// CHECK-NOT: lsl{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shl2(long arg) { + // Note: Shl(x, 1) is transformed to Add(x, x), so test Shl(x, 2). + return arg << 2; + } + + /// CHECK-START-ARM: long Main.shl31(long) disassembly (after) + /// CHECK: lsl{{s?|.w}} <<oh:r\d+>>, {{r\d+}}, #31 + /// CHECK: orr.w <<oh>>, <<oh>>, <<low:r\d+>>, lsr #1 + /// CHECK: lsl{{s?|.w}} {{r\d+}}, <<low>>, #31 + + /// CHECK-START-ARM: long Main.shl31(long) disassembly (after) + /// CHECK-NOT: lsl{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shl31(long arg) { + return arg << 31; + } + + /// CHECK-START-ARM: long Main.shl32(long) disassembly (after) + /// CHECK-DAG: mov {{r\d+}}, {{r\d+}} + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.shl32(long) disassembly (after) + /// CHECK-NOT: lsl{{s?|.w}} + + public static long shl32(long arg) { + return arg << 32; + } + + /// CHECK-START-ARM: long Main.shl33(long) disassembly (after) + /// CHECK-DAG: lsl{{s?|.w}} {{r\d+}}, <<high:r\d+>>, #1 + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.shl33(long) disassembly (after) + /// CHECK-NOT: lsl{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shl33(long arg) { + return arg << 33; + } + + /// CHECK-START-ARM: long Main.shl63(long) disassembly (after) + /// CHECK-DAG: lsl{{s?|.w}} {{r\d+}}, <<high:r\d+>>, #31 + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.shl63(long) disassembly (after) + /// CHECK-NOT: lsl{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shl63(long arg) { + return arg << 63; + } + + /// CHECK-START-ARM: long Main.shr1(long) disassembly (after) + /// CHECK: lsr{{s?|.w}} <<ol:r\d+>>, {{r\d+}}, #1 + /// CHECK: orr.w <<ol>>, <<ol>>, <<high:r\d+>>, lsl #31 + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high>>, #1 + + /// CHECK-START-ARM: long Main.shr1(long) disassembly (after) + /// CHECK-NOT: asr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shr1(long arg) { + return arg >> 1; + } + + /// CHECK-START-ARM: long Main.shr31(long) disassembly (after) + /// CHECK: lsr{{s?|.w}} <<ol:r\d+>>, {{r\d+}}, #31 + /// CHECK: orr.w <<ol>>, <<ol>>, <<high:r\d+>>, lsl #1 + /// CHECK: asr{{s?|.w}} {{r\d+}}, <<high>>, #31 + + /// CHECK-START-ARM: long Main.shr31(long) disassembly (after) + /// CHECK-NOT: asr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shr31(long arg) { + return arg >> 31; + } + + /// CHECK-START-ARM: long Main.shr32(long) disassembly (after) + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high:r\d+>>, #31 + /// CHECK-DAG: mov {{r\d+}}, <<high>> + + /// CHECK-START-ARM: long Main.shr32(long) disassembly (after) + /// CHECK-NOT: asr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + /// CHECK-NOT: lsr{{s?|.w}} + + public static long shr32(long arg) { + return arg >> 32; + } + + /// CHECK-START-ARM: long Main.shr33(long) disassembly (after) + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high:r\d+>>, #1 + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high>>, #31 + + /// CHECK-START-ARM: long Main.shr33(long) disassembly (after) + /// CHECK-NOT: asr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shr33(long arg) { + return arg >> 33; + } + + /// CHECK-START-ARM: long Main.shr63(long) disassembly (after) + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high:r\d+>>, #31 + /// CHECK-DAG: asr{{s?|.w}} {{r\d+}}, <<high>>, #31 + + /// CHECK-START-ARM: long Main.shr63(long) disassembly (after) + /// CHECK-NOT: asr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long shr63(long arg) { + return arg >> 63; + } + + /// CHECK-START-ARM: long Main.ushr1(long) disassembly (after) + /// CHECK: lsr{{s?|.w}} <<ol:r\d+>>, {{r\d+}}, #1 + /// CHECK: orr.w <<ol>>, <<ol>>, <<high:r\d+>>, lsl #31 + /// CHECK-DAG: lsr{{s?|.w}} {{r\d+}}, <<high>>, #1 + + /// CHECK-START-ARM: long Main.ushr1(long) disassembly (after) + /// CHECK-NOT: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long ushr1(long arg) { + return arg >>> 1; + } + + /// CHECK-START-ARM: long Main.ushr31(long) disassembly (after) + /// CHECK: lsr{{s?|.w}} <<ol:r\d+>>, {{r\d+}}, #31 + /// CHECK: orr.w <<ol>>, <<ol>>, <<high:r\d+>>, lsl #1 + /// CHECK: lsr{{s?|.w}} {{r\d+}}, <<high>>, #31 + + /// CHECK-START-ARM: long Main.ushr31(long) disassembly (after) + /// CHECK-NOT: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long ushr31(long arg) { + return arg >>> 31; + } + + /// CHECK-START-ARM: long Main.ushr32(long) disassembly (after) + /// CHECK-DAG: mov {{r\d+}}, {{r\d+}} + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.ushr32(long) disassembly (after) + /// CHECK-NOT: lsr{{s?|.w}} + + public static long ushr32(long arg) { + return arg >>> 32; + } + + /// CHECK-START-ARM: long Main.ushr33(long) disassembly (after) + /// CHECK-DAG: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, #1 + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.ushr33(long) disassembly (after) + /// CHECK-NOT: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long ushr33(long arg) { + return arg >>> 33; + } + + /// CHECK-START-ARM: long Main.ushr63(long) disassembly (after) + /// CHECK-DAG: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, #31 + /// CHECK-DAG: mov{{s?|.w}} {{r\d+}}, #0 + + /// CHECK-START-ARM: long Main.ushr63(long) disassembly (after) + /// CHECK-NOT: lsr{{s?|.w}} {{r\d+}}, {{r\d+}}, {{r\d+}} + + public static long ushr63(long arg) { + return arg >>> 63; + } + /** * Test that the `-1` constant is not synthesized in a register and that we * instead simply switch between `add` and `sub` instructions with the @@ -311,5 +484,38 @@ public class Main { assertLongEquals(xor0xf00000000000000f(longArg), 0xe23456788765432eL); assertLongEquals(14, addM1(7)); + + assertLongEquals(shl2(longArg), 0x48d159e21d950c84L); + assertLongEquals(shl31(longArg), 0x43b2a19080000000L); + assertLongEquals(shl32(longArg), 0x8765432100000000L); + assertLongEquals(shl33(longArg), 0x0eca864200000000L); + assertLongEquals(shl63(longArg), 0x8000000000000000L); + assertLongEquals(shl2(~longArg), 0xb72ea61de26af378L); + assertLongEquals(shl31(~longArg), 0xbc4d5e6f00000000L); + assertLongEquals(shl32(~longArg), 0x789abcde00000000L); + assertLongEquals(shl33(~longArg), 0xf13579bc00000000L); + assertLongEquals(shl63(~longArg), 0x0000000000000000L); + + assertLongEquals(shr1(longArg), 0x091a2b3c43b2a190L); + assertLongEquals(shr31(longArg), 0x000000002468acf1L); + assertLongEquals(shr32(longArg), 0x0000000012345678L); + assertLongEquals(shr33(longArg), 0x00000000091a2b3cL); + assertLongEquals(shr63(longArg), 0x0000000000000000L); + assertLongEquals(shr1(~longArg), 0xf6e5d4c3bc4d5e6fL); + assertLongEquals(shr31(~longArg), 0xffffffffdb97530eL); + assertLongEquals(shr32(~longArg), 0xffffffffedcba987L); + assertLongEquals(shr33(~longArg), 0xfffffffff6e5d4c3L); + assertLongEquals(shr63(~longArg), 0xffffffffffffffffL); + + assertLongEquals(ushr1(longArg), 0x091a2b3c43b2a190L); + assertLongEquals(ushr31(longArg), 0x000000002468acf1L); + assertLongEquals(ushr32(longArg), 0x0000000012345678L); + assertLongEquals(ushr33(longArg), 0x00000000091a2b3cL); + assertLongEquals(ushr63(longArg), 0x0000000000000000L); + assertLongEquals(ushr1(~longArg), 0x76e5d4c3bc4d5e6fL); + assertLongEquals(ushr31(~longArg), 0x00000001db97530eL); + assertLongEquals(ushr32(~longArg), 0x00000000edcba987L); + assertLongEquals(ushr33(~longArg), 0x0000000076e5d4c3L); + assertLongEquals(ushr63(~longArg), 0x0000000000000001L); } } diff --git a/test/543-checker-dce-trycatch/expected.txt b/test/543-checker-dce-trycatch/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/543-checker-dce-trycatch/expected.txt diff --git a/test/543-checker-dce-trycatch/info.txt b/test/543-checker-dce-trycatch/info.txt new file mode 100644 index 0000000000..e541938f68 --- /dev/null +++ b/test/543-checker-dce-trycatch/info.txt @@ -0,0 +1 @@ +Tests removal of try/catch blocks by DCE.
\ No newline at end of file diff --git a/test/543-checker-dce-trycatch/smali/TestCase.smali b/test/543-checker-dce-trycatch/smali/TestCase.smali new file mode 100644 index 0000000000..44e907d80e --- /dev/null +++ b/test/543-checker-dce-trycatch/smali/TestCase.smali @@ -0,0 +1,317 @@ +# Copyright (C) 2015 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. + +.class public LTestCase; +.super Ljava/lang/Object; + +.method private static $inline$False()Z + .registers 1 + const/4 v0, 0x0 + return v0 +.end method + +# Test a case when one entering TryBoundary is dead but the rest of the try +# block remains live. + +## CHECK-START: int TestCase.testDeadEntry(int, int, int, int) dead_code_elimination_final (before) +## CHECK: Add + +## CHECK-START: int TestCase.testDeadEntry(int, int, int, int) dead_code_elimination_final (before) +## CHECK: TryBoundary kind:entry +## CHECK: TryBoundary kind:entry +## CHECK-NOT: TryBoundary kind:entry + +## CHECK-START: int TestCase.testDeadEntry(int, int, int, int) dead_code_elimination_final (after) +## CHECK-NOT: Add + +## CHECK-START: int TestCase.testDeadEntry(int, int, int, int) dead_code_elimination_final (after) +## CHECK: TryBoundary kind:entry +## CHECK-NOT: TryBoundary kind:entry + +.method public static testDeadEntry(IIII)I + .registers 5 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + if-eqz v0, :else + + add-int/2addr p0, p1 + + :try_start + div-int/2addr p0, p2 + + :else + div-int/2addr p0, p3 + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :return + return p0 + + :catch_all + const/4 p0, -0x1 + goto :return + +.end method + +# Test a case when one exiting TryBoundary is dead but the rest of the try +# block remains live. + +## CHECK-START: int TestCase.testDeadExit(int, int, int, int) dead_code_elimination_final (before) +## CHECK: Add + +## CHECK-START: int TestCase.testDeadExit(int, int, int, int) dead_code_elimination_final (before) +## CHECK: TryBoundary kind:exit +## CHECK: TryBoundary kind:exit +## CHECK-NOT: TryBoundary kind:exit + +## CHECK-START: int TestCase.testDeadExit(int, int, int, int) dead_code_elimination_final (after) +## CHECK-NOT: Add + +## CHECK-START: int TestCase.testDeadExit(int, int, int, int) dead_code_elimination_final (after) +## CHECK: TryBoundary kind:exit +## CHECK-NOT: TryBoundary kind:exit + +.method public static testDeadExit(IIII)I + .registers 5 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + :try_start + div-int/2addr p0, p2 + + if-nez v0, :else + + div-int/2addr p0, p3 + goto :return + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :else + add-int/2addr p0, p1 + + :return + return p0 + + :catch_all + const/4 p0, -0x1 + goto :return + +.end method + +# Test that a catch block remains live and consistent if some of try blocks +# throwing into it are removed. + +## CHECK-START: int TestCase.testOneTryBlockDead(int, int, int, int) dead_code_elimination_final (before) +## CHECK: TryBoundary kind:entry +## CHECK: TryBoundary kind:entry +## CHECK-NOT: TryBoundary kind:entry + +## CHECK-START: int TestCase.testOneTryBlockDead(int, int, int, int) dead_code_elimination_final (before) +## CHECK: TryBoundary kind:exit +## CHECK: TryBoundary kind:exit +## CHECK-NOT: TryBoundary kind:exit + +## CHECK-START: int TestCase.testOneTryBlockDead(int, int, int, int) dead_code_elimination_final (after) +## CHECK: TryBoundary kind:entry +## CHECK-NOT: TryBoundary kind:entry + +## CHECK-START: int TestCase.testOneTryBlockDead(int, int, int, int) dead_code_elimination_final (after) +## CHECK: TryBoundary kind:exit +## CHECK-NOT: TryBoundary kind:exit + +.method public static testOneTryBlockDead(IIII)I + .registers 5 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + :try_start_1 + div-int/2addr p0, p2 + :try_end_1 + .catchall {:try_start_1 .. :try_end_1} :catch_all + + if-eqz v0, :return + + :try_start_2 + div-int/2addr p0, p3 + :try_end_2 + .catchall {:try_start_2 .. :try_end_2} :catch_all + + :return + return p0 + + :catch_all + const/4 p0, -0x1 + goto :return + +.end method + +# Test that try block membership is recomputed. In this test case, the try entry +# stored with the merge block gets deleted and SSAChecker would fail if it was +# not replaced with the try entry from the live branch. + +.method public static testRecomputeTryMembership(IIII)I + .registers 5 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + if-eqz v0, :else + + # Dead branch + :try_start + div-int/2addr p0, p1 + goto :merge + + # Live branch + :else + div-int/2addr p0, p2 + + # Merge block. Make complex so it does not get merged with the live branch. + :merge + div-int/2addr p0, p3 + if-eqz p0, :else2 + div-int/2addr p0, p3 + :else2 + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :return + return p0 + + :catch_all + const/4 p0, -0x1 + goto :return + +.end method + +# Test that DCE removes catch phi uses of instructions defined in dead try blocks. + +## CHECK-START: int TestCase.testCatchPhiInputs_DefinedInTryBlock(int, int, int, int) dead_code_elimination_final (before) +## CHECK-DAG: <<Arg0:i\d+>> ParameterValue +## CHECK-DAG: <<Arg1:i\d+>> ParameterValue +## CHECK-DAG: <<Const0xa:i\d+>> IntConstant 10 +## CHECK-DAG: <<Const0xb:i\d+>> IntConstant 11 +## CHECK-DAG: <<Const0xc:i\d+>> IntConstant 12 +## CHECK-DAG: <<Const0xd:i\d+>> IntConstant 13 +## CHECK-DAG: <<Const0xe:i\d+>> IntConstant 14 +## CHECK-DAG: <<Add:i\d+>> Add [<<Arg0>>,<<Arg1>>] +## CHECK-DAG: Phi [<<Const0xa>>,<<Const0xb>>,<<Const0xd>>] reg:1 is_catch_phi:true +## CHECK-DAG: Phi [<<Add>>,<<Const0xc>>,<<Const0xe>>] reg:2 is_catch_phi:true + +## CHECK-START: int TestCase.testCatchPhiInputs_DefinedInTryBlock(int, int, int, int) dead_code_elimination_final (after) +## CHECK-DAG: <<Const0xb:i\d+>> IntConstant 11 +## CHECK-DAG: <<Const0xc:i\d+>> IntConstant 12 +## CHECK-DAG: <<Const0xd:i\d+>> IntConstant 13 +## CHECK-DAG: <<Const0xe:i\d+>> IntConstant 14 +## CHECK-DAG: Phi [<<Const0xb>>,<<Const0xd>>] reg:1 is_catch_phi:true +## CHECK-DAG: Phi [<<Const0xc>>,<<Const0xe>>] reg:2 is_catch_phi:true + +.method public static testCatchPhiInputs_DefinedInTryBlock(IIII)I + .registers 7 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + if-eqz v0, :else + + shr-int/2addr p2, p3 + + :try_start + const v1, 0xa # dead catch phi input, defined in entry block + add-int v2, p0, p1 # dead catch phi input, defined in the dead block + div-int/2addr p0, v2 + + :else + const v1, 0xb # live catch phi input + const v2, 0xc # live catch phi input + div-int/2addr p0, p3 + + const v1, 0xd # live catch phi input + const v2, 0xe # live catch phi input + div-int/2addr p0, p1 + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :return + return p0 + + :catch_all + sub-int p0, v1, v2 # use catch phi values + goto :return + +.end method + +# Test that DCE does not remove catch phi uses of instructions defined outside +# dead try blocks. + +## CHECK-START: int TestCase.testCatchPhiInputs_DefinedOutsideTryBlock(int, int, int, int) dead_code_elimination_final (before) +## CHECK-DAG: <<Arg0:i\d+>> ParameterValue +## CHECK-DAG: <<Arg1:i\d+>> ParameterValue +## CHECK-DAG: <<Const0xa:i\d+>> IntConstant 10 +## CHECK-DAG: <<Const0xb:i\d+>> IntConstant 11 +## CHECK-DAG: <<Const0xc:i\d+>> IntConstant 12 +## CHECK-DAG: <<Const0xd:i\d+>> IntConstant 13 +## CHECK-DAG: <<Const0xe:i\d+>> IntConstant 14 +## CHECK-DAG: <<Const0xf:i\d+>> IntConstant 15 +## CHECK-DAG: Phi [<<Const0xa>>,<<Const0xb>>,<<Const0xd>>] reg:1 is_catch_phi:true +## CHECK-DAG: Phi [<<Const0xf>>,<<Const0xc>>,<<Const0xe>>] reg:2 is_catch_phi:true + +## CHECK-START: int TestCase.testCatchPhiInputs_DefinedOutsideTryBlock(int, int, int, int) dead_code_elimination_final (after) +## CHECK-DAG: <<Const0xa:i\d+>> IntConstant 10 +## CHECK-DAG: <<Const0xb:i\d+>> IntConstant 11 +## CHECK-DAG: <<Const0xc:i\d+>> IntConstant 12 +## CHECK-DAG: <<Const0xd:i\d+>> IntConstant 13 +## CHECK-DAG: <<Const0xe:i\d+>> IntConstant 14 +## CHECK-DAG: <<Const0xf:i\d+>> IntConstant 15 +## CHECK-DAG: Phi [<<Const0xa>>,<<Const0xb>>,<<Const0xd>>] reg:1 is_catch_phi:true +## CHECK-DAG: Phi [<<Const0xf>>,<<Const0xc>>,<<Const0xe>>] reg:2 is_catch_phi:true + +.method public static testCatchPhiInputs_DefinedOutsideTryBlock(IIII)I + .registers 7 + + invoke-static {}, LTestCase;->$inline$False()Z + move-result v0 + + if-eqz v0, :else + + shr-int/2addr p2, p3 + + :try_start + const v1, 0xa # dead catch phi input, defined in entry block + const v2, 0xf # dead catch phi input, defined in entry block + div-int/2addr p0, v2 + + :else + const v1, 0xb # live catch phi input + const v2, 0xc # live catch phi input + div-int/2addr p0, p3 + + const v1, 0xd # live catch phi input + const v2, 0xe # live catch phi input + div-int/2addr p0, p1 + :try_end + .catchall {:try_start .. :try_end} :catch_all + + :return + return p0 + + :catch_all + sub-int p0, v1, v2 # use catch phi values + goto :return + +.end method diff --git a/test/543-checker-dce-trycatch/src/Main.java b/test/543-checker-dce-trycatch/src/Main.java new file mode 100644 index 0000000000..6e73d0dbd1 --- /dev/null +++ b/test/543-checker-dce-trycatch/src/Main.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 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 { + + // Workaround for b/18051191. + class InnerClass {} + + static boolean $inline$False() { return false; } + + // DCE should only merge blocks where the first ends with a Goto. + // SSAChecker will fail if the following Throw->TryBoundary blocks are merged. + public static void doNotMergeThrow(String str) { + try { + throw new Exception(str); + } catch (Exception ex) { + return; + } + } + + // Test deletion of all try/catch blocks. Multiple catch blocks test deletion + // where TryBoundary still has exception handler successors after having removed + // some already. + + /// CHECK-START: void Main.testDeadTryCatch(boolean) dead_code_elimination_final (after) + /// CHECK-NOT: TryBoundary + + /// CHECK-START: void Main.testDeadTryCatch(boolean) dead_code_elimination_final (after) + /// CHECK: begin_block + /// CHECK: begin_block + /// CHECK: begin_block + /// CHECK-NOT: begin_block + + public static void testDeadTryCatch(boolean val) { + if ($inline$False()) { + try { + if (val) { + throw new ArithmeticException(); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } catch (ArithmeticException ex) { + System.out.println("Unexpected AE catch"); + } catch (ArrayIndexOutOfBoundsException ex) { + System.out.println("Unexpected AIIOB catch"); + } + } + } + + public static void main(String[] args) { + + } +}
\ No newline at end of file diff --git a/test/548-checker-inlining-and-dce/expected.txt b/test/548-checker-inlining-and-dce/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/548-checker-inlining-and-dce/expected.txt diff --git a/test/548-checker-inlining-and-dce/info.txt b/test/548-checker-inlining-and-dce/info.txt new file mode 100644 index 0000000000..3255d6bbc4 --- /dev/null +++ b/test/548-checker-inlining-and-dce/info.txt @@ -0,0 +1 @@ +Test that inlining works when code preventing inlining is eliminated by DCE. diff --git a/test/548-checker-inlining-and-dce/src/Main.java b/test/548-checker-inlining-and-dce/src/Main.java new file mode 100644 index 0000000000..38fdcc0b94 --- /dev/null +++ b/test/548-checker-inlining-and-dce/src/Main.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 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 { + + private void inlinedForNull(Iterable it) { + if (it != null) { + // We're not inlining invoke-interface at the moment. + it.iterator(); + } + } + + private void inlinedForFalse(boolean value, Iterable it) { + if (value) { + // We're not inlining invoke-interface at the moment. + it.iterator(); + } + } + + /// CHECK-START: void Main.testInlinedForFalseInlined(java.lang.Iterable) inliner (before) + /// CHECK: InvokeStaticOrDirect + + /// CHECK-START: void Main.testInlinedForFalseInlined(java.lang.Iterable) inliner (after) + /// CHECK-NOT: InvokeStaticOrDirect + /// CHECK-NOT: InvokeInterface + + public void testInlinedForFalseInlined(Iterable it) { + inlinedForFalse(false, it); + } + + /// CHECK-START: void Main.testInlinedForFalseNotInlined(java.lang.Iterable) inliner (before) + /// CHECK: InvokeStaticOrDirect + + /// CHECK-START: void Main.testInlinedForFalseNotInlined(java.lang.Iterable) inliner (after) + /// CHECK: InvokeStaticOrDirect + + public void testInlinedForFalseNotInlined(Iterable it) { + inlinedForFalse(true, it); + } + + /// CHECK-START: void Main.testInlinedForNullInlined(java.lang.Iterable) inliner (before) + /// CHECK: InvokeStaticOrDirect + + /// CHECK-START: void Main.testInlinedForNullInlined(java.lang.Iterable) inliner (after) + /// CHECK-NOT: InvokeStaticOrDirect + /// CHECK-NOT: InvokeInterface + + public void testInlinedForNullInlined(Iterable it) { + inlinedForNull(null); + } + + /// CHECK-START: void Main.testInlinedForNullNotInlined(java.lang.Iterable) inliner (before) + /// CHECK: InvokeStaticOrDirect + + /// CHECK-START: void Main.testInlinedForNullNotInlined(java.lang.Iterable) inliner (after) + /// CHECK: InvokeStaticOrDirect + + public void testInlinedForNullNotInlined(Iterable it) { + inlinedForNull(it); + } + + public static void main(String[] args) { + Main m = new Main(); + Iterable it = new Iterable() { + public java.util.Iterator iterator() { return null; } + }; + m.testInlinedForFalseInlined(it); + m.testInlinedForFalseNotInlined(it); + m.testInlinedForNullInlined(it); + m.testInlinedForNullNotInlined(it); + } +} diff --git a/test/960-default-smali/build b/test/960-default-smali/build index 3946de3787..4dc848cfa6 100755 --- a/test/960-default-smali/build +++ b/test/960-default-smali/build @@ -20,18 +20,19 @@ set -e # Generate the smali Main.smali file or fail ${ANDROID_BUILD_TOP}/art/test/utils/python/generate_smali_main.py ./smali -USES_JAVA="false" +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" if [[ $ARGS == *"--jvm"* ]]; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" elif [[ "$USE_JACK" == "true" ]]; then if $JACK -D jack.java.source.version=1.8 >& /dev/null; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" else echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 fi fi -if [[ "$USES_JAVA" == "true" ]]; then +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then # We are compiling java code, create it. mkdir -p src ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src diff --git a/test/961-default-iface-resolution-generated/build b/test/961-default-iface-resolution-generated/build index 03cc62459a..b4ced3e82e 100755 --- a/test/961-default-iface-resolution-generated/build +++ b/test/961-default-iface-resolution-generated/build @@ -31,18 +31,19 @@ mkdir -p ./smali # Generate the smali files and expected.txt or fail ./util-src/generate_smali.py ./smali ./expected.txt -USES_JAVA="false" +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" if [[ $ARGS == *"--jvm"* ]]; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" elif [[ $USE_JACK == "true" ]]; then if "$JACK" -D jack.java.source.version=1.8 >& /dev/null; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" else echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 fi fi -if [[ "$USES_JAVA" == "true" ]]; then +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then # We are compiling java code, create it. mkdir -p src ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src diff --git a/test/962-iface-static/build b/test/962-iface-static/build index 24e2feb228..e17272f769 100755 --- a/test/962-iface-static/build +++ b/test/962-iface-static/build @@ -17,18 +17,19 @@ # make us exit on a failure set -e -USES_JAVA="false" +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" if [[ $@ == *"--jvm"* ]]; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" elif [[ "$USE_JACK" == "true" ]]; then if $JACK -D jack.java.source.version=1.8 2>/dev/null; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" else echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 fi fi -if [[ "$USES_JAVA" == "true" ]]; then +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then # We are compiling java code, create it. mkdir -p src ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src diff --git a/test/963-default-range-smali/build b/test/963-default-range-smali/build index 24e2feb228..e17272f769 100755 --- a/test/963-default-range-smali/build +++ b/test/963-default-range-smali/build @@ -17,18 +17,19 @@ # make us exit on a failure set -e -USES_JAVA="false" +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" if [[ $@ == *"--jvm"* ]]; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" elif [[ "$USE_JACK" == "true" ]]; then if $JACK -D jack.java.source.version=1.8 2>/dev/null; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" else echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 fi fi -if [[ "$USES_JAVA" == "true" ]]; then +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then # We are compiling java code, create it. mkdir -p src ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src diff --git a/test/964-default-iface-init-generated/build b/test/964-default-iface-init-generated/build index d916f1b8e9..0780da14e2 100755 --- a/test/964-default-iface-init-generated/build +++ b/test/964-default-iface-init-generated/build @@ -29,18 +29,19 @@ trap 'restore_ulimit' ERR # Generate the smali files and expected.txt or fail ./util-src/generate_smali.py ./smali ./expected.txt -USES_JAVA="false" +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" if [[ $@ == *"--jvm"* ]]; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" elif [[ "$USE_JACK" == "true" ]]; then if $JACK -D jack.java.source.version=1.8 2>/dev/null; then - USES_JAVA="true" + USES_JAVA_SOURCE="true" else echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 fi fi -if [[ "$USES_JAVA" == "true" ]]; then +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then # We are compiling java code, create it. mkdir -p src ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src diff --git a/test/965-default-verify/build b/test/965-default-verify/build new file mode 100755 index 0000000000..5ba54380df --- /dev/null +++ b/test/965-default-verify/build @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# Should we compile with Java source code. By default we will use Smali. +USES_JAVA_SOURCE="false" +if [[ $@ == *"--jvm"* ]]; then + USES_JAVA_SOURCE="true" +elif [[ "$USE_JACK" == "true" ]]; then + if $JACK -D jack.java.source.version=1.8 2>/dev/null; then + USES_JAVA_SOURCE="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA_SOURCE" == "true" ]]; then + # We are compiling Java code, create it. + mkdir -p src + mkdir -p src2 + ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src + # Move build-src to src and the src copies to src2. This is needed because of + # how our default build script works and we wanted the java and smali code + # to be the same in the smali files. + for f in `find ./build-src -type f -name "*.java" | xargs -i basename \{\}`; do + mv ./src/$f ./src2/$f + mv ./build-src/$f ./src/$f + done + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" +fi + +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods diff --git a/test/965-default-verify/build-src/Statics.java b/test/965-default-verify/build-src/Statics.java new file mode 100644 index 0000000000..300aeecca7 --- /dev/null +++ b/test/965-default-verify/build-src/Statics.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 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. + */ + +class Statics { + public static void nonexistantFunction() { + System.out.println("I don't exist"); + } +} + diff --git a/test/965-default-verify/expected.txt b/test/965-default-verify/expected.txt new file mode 100644 index 0000000000..b31314ffc6 --- /dev/null +++ b/test/965-default-verify/expected.txt @@ -0,0 +1,15 @@ +Create Main instance +Calling functions on concrete Main +Calling verifiable function on Main +Hello +Calling unverifiable function on Main +Expected NSME Thrown on Main +Calling verifiable function on Main +Hello +Calling functions on interface Iface +Calling verifiable function on Iface +Hello +Calling unverifiable function on Iface +Expected NSME Thrown on Iface +Calling verifiable function on Iface +Hello diff --git a/test/965-default-verify/info.txt b/test/965-default-verify/info.txt new file mode 100644 index 0000000000..2ccabf565b --- /dev/null +++ b/test/965-default-verify/info.txt @@ -0,0 +1,8 @@ +Smali-based tests for verification interaction with experimental interface +default methods. + +build-src contains java files that are needed if you are to compile with javac +since it is much more proactive about finding likely runtime errors then smali. + +To run with --jvm you must export JAVA_HOME to a Java 8 Language installation +and pass the --use-java-home to run-test diff --git a/test/965-default-verify/run b/test/965-default-verify/run new file mode 100755 index 0000000000..8944ea92d3 --- /dev/null +++ b/test/965-default-verify/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/965-default-verify/smali/Iface.smali b/test/965-default-verify/smali/Iface.smali new file mode 100644 index 0000000000..74799a6cf3 --- /dev/null +++ b/test/965-default-verify/smali/Iface.smali @@ -0,0 +1,40 @@ +# /* +# * Copyright (C) 2015 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 interface Iface { +# public default String sayHi() { +# return "Hello"; +# } +# +# public default void verificationSoftFail() { +# Statics.nonexistantFunction(); +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hello" + return-object v0 +.end method + +.method public verificationSoftFail()V + .locals 1 + invoke-static {}, LStatics;->nonexistantFunction()V + return-void +.end method diff --git a/test/965-default-verify/smali/Main.smali b/test/965-default-verify/smali/Main.smali new file mode 100644 index 0000000000..8e9070692d --- /dev/null +++ b/test/965-default-verify/smali/Main.smali @@ -0,0 +1,179 @@ +# /* +# * Copyright (C) 2015 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. +# */ +# +# class Main implements Iface { +# public static void main(String[] args) { +# System.out.println("Create Main instance"); +# Main m = new Main(); +# System.out.println("Calling functions on concrete Main"); +# callMain(m); +# System.out.println("Calling functions on interface Iface"); +# callIface(m); +# } +# +# public static void callMain(Main m) { +# System.out.println("Calling verifiable function on Main"); +# System.out.println(m.sayHi()); +# System.out.println("Calling unverifiable function on Main"); +# try { +# m.verificationSoftFail(); +# System.out.println("Unexpected no error Thrown on Main"); +# } catch (NoSuchMethodError e) { +# System.out.println("Expected NSME Thrown on Main"); +# } catch (Throwable e) { +# System.out.println("Unexpected Error Thrown on Main"); +# e.printStackTrace(System.out); +# } +# System.out.println("Calling verifiable function on Main"); +# System.out.println(m.sayHi()); +# return; +# } +# +# public static void callIface(Iface m) { +# System.out.println("Calling verifiable function on Iface"); +# System.out.println(m.sayHi()); +# System.out.println("Calling unverifiable function on Iface"); +# try { +# m.verificationSoftFail(); +# System.out.println("Unexpected no error Thrown on Iface"); +# } catch (NoSuchMethodError e) { +# System.out.println("Expected NSME Thrown on Iface"); +# } catch (Throwable e) { +# System.out.println("Unexpected Error Thrown on Iface"); +# e.printStackTrace(System.out); +# } +# System.out.println("Calling verifiable function on Iface"); +# System.out.println(m.sayHi()); +# return; +# } +# } + +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v0, "Create Main instance" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v2, LMain; + invoke-direct {v2}, LMain;-><init>()V + + const-string v0, "Calling functions on concrete Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callMain(LMain;)V + + const-string v0, "Calling functions on interface Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface(LIface;)V + + return-void +.end method + +.method public static callIface(LIface;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling verifiable function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling unverifiable function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface;->verificationSoftFail()V + + const-string v0, "Unexpected no error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/NoSuchMethodError; {:try_start .. :try_end} :NSME_error_start + .catch Ljava/lang/Throwable; {:try_start .. :try_end} :other_error_start + :NSME_error_start + const-string v0, "Expected NSME Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :other_error_start + move-exception v2 + const-string v0, "Unexpected Error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-virtual {v2,v1}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + goto :error_end + :error_end + const-string v0, "Calling verifiable function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method + +.method public static callMain(LMain;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling verifiable function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling unverifiable function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-virtual {p0}, LMain;->verificationSoftFail()V + + const-string v0, "Unexpected no error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/NoSuchMethodError; {:try_start .. :try_end} :NSME_error_start + .catch Ljava/lang/Throwable; {:try_start .. :try_end} :other_error_start + :NSME_error_start + const-string v0, "Expected NSME Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :other_error_start + move-exception v2 + const-string v0, "Unexpected Error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-virtual {v2,v1}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + goto :error_end + :error_end + const-string v0, "Calling verifiable function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/965-default-verify/smali/Statics.smali b/test/965-default-verify/smali/Statics.smali new file mode 100644 index 0000000000..1e8cac034a --- /dev/null +++ b/test/965-default-verify/smali/Statics.smali @@ -0,0 +1,30 @@ +# /* +# * Copyright (C) 2015 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. +# */ +# +# class Statics { +# // public static void nonexistantFunction() { +# // System.out.println("I don't exist"); +# // } +# } +# +.class public LStatics; +.super Ljava/lang/Object; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method diff --git a/test/966-default-conflict/build b/test/966-default-conflict/build new file mode 100755 index 0000000000..e66e8409c6 --- /dev/null +++ b/test/966-default-conflict/build @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p src + mkdir -p classes + ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src + # Build with the non-conflicting version + ${JAVAC} -implicit:none -d classes src/Iface.java build-src/Iface2.java src/Main.java + rm classes/Iface2.class + # Build with the conflicting version + ${JAVAC} -implicit:none -cp classes -d classes src/Iface2.java +else + ./default-build "$@" --experimental default-methods +fi diff --git a/test/966-default-conflict/build-src/Iface2.java b/test/966-default-conflict/build-src/Iface2.java new file mode 100644 index 0000000000..8d97df8385 --- /dev/null +++ b/test/966-default-conflict/build-src/Iface2.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 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. + */ + +// We extend Iface so that javac will not complain that Iface2 does not declare a sayHi method or +// has a soft-conflict on the sayHi method if it did. +public interface Iface2 extends Iface { + // public default String sayHi() { + // return "hello"; + // } +} + + diff --git a/test/966-default-conflict/expected.txt b/test/966-default-conflict/expected.txt new file mode 100644 index 0000000000..fad2c25979 --- /dev/null +++ b/test/966-default-conflict/expected.txt @@ -0,0 +1,18 @@ +Create Main instance +Calling functions on concrete Main +Calling non-conflicting function on Main +CHARGE +Calling conflicting function on Main +Expected ICCE Thrown on Main +Calling non-conflicting function on Main +CHARGE +Calling functions on interface Iface +Calling non-conflicting function on Iface +CHARGE +Calling conflicting function on Iface +Expected ICCE Thrown on Iface +Calling non-conflicting function on Iface +CHARGE +Calling functions on interface Iface2 +Calling conflicting function on Iface2 +Expected ICCE Thrown on Iface2 diff --git a/test/966-default-conflict/info.txt b/test/966-default-conflict/info.txt new file mode 100644 index 0000000000..2b67657dcc --- /dev/null +++ b/test/966-default-conflict/info.txt @@ -0,0 +1,6 @@ +Smali-based tests for experimental interface static methods. + +Tests handling of default method conflicts. + +To run with --jvm you must export JAVA_HOME to a Java 8 Language installation +and pass the --use-java-home to run-test diff --git a/test/966-default-conflict/run b/test/966-default-conflict/run new file mode 100755 index 0000000000..8944ea92d3 --- /dev/null +++ b/test/966-default-conflict/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/966-default-conflict/smali/Iface.smali b/test/966-default-conflict/smali/Iface.smali new file mode 100644 index 0000000000..e996b3a4f4 --- /dev/null +++ b/test/966-default-conflict/smali/Iface.smali @@ -0,0 +1,39 @@ +# /* +# * Copyright (C) 2015 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 interface Iface { +# public default String sayHi() { +# return "Hi"; +# } +# public default String charge() { +# return "CHARGE"; +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hi" + return-object v0 +.end method + +.method public charge()Ljava/lang/String; + .locals 1 + const-string v0, "CHARGE" + return-object v0 +.end method diff --git a/test/966-default-conflict/smali/Iface2.smali b/test/966-default-conflict/smali/Iface2.smali new file mode 100644 index 0000000000..82fa547dea --- /dev/null +++ b/test/966-default-conflict/smali/Iface2.smali @@ -0,0 +1,31 @@ +# /* +# * Copyright (C) 2015 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 interface Iface2 { +# public default String sayHi() { +# return "hello"; +# } +# } + +.class public abstract interface LIface2; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "hello" + return-object v0 +.end method + diff --git a/test/966-default-conflict/smali/Main.smali b/test/966-default-conflict/smali/Main.smali new file mode 100644 index 0000000000..ce974d8135 --- /dev/null +++ b/test/966-default-conflict/smali/Main.smali @@ -0,0 +1,227 @@ +# /* +# * Copyright (C) 2015 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. +# */ +# +# class Main implements Iface, Iface2 { +# public static void main(String[] args) { +# System.out.println("Create Main instance"); +# Main m = new Main(); +# System.out.println("Calling functions on concrete Main"); +# callMain(m); +# System.out.println("Calling functions on interface Iface"); +# callIface(m); +# System.out.println("Calling functions on interface Iface2"); +# callIface2(m); +# } +# +# public static void callMain(Main m) { +# System.out.println("Calling non-conflicting function on Main"); +# System.out.println(m.charge()); +# System.out.println("Calling conflicting function on Main"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Main"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Main"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Main"); +# } +# System.out.println("Calling non-conflicting function on Main"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface(Iface m) { +# System.out.println("Calling non-conflicting function on Iface"); +# System.out.println(m.charge()); +# System.out.println("Calling conflicting function on Iface"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Iface"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Iface"); +# } +# System.out.println("Calling non-conflicting function on Iface"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface2(Iface2 m) { +# System.out.println("Calling conflicting function on Iface2"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface2"); +# } catch (AbstractMethodError e) { +# System.out.println("Unexpected AME Thrown on Iface2"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Expected ICCE Thrown on Iface2"); +# } +# return; +# } +# } + +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; +.implements LIface2; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v0, "Create Main instance" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v2, LMain; + invoke-direct {v2}, LMain;-><init>()V + + const-string v0, "Calling functions on concrete Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callMain(LMain;)V + + const-string v0, "Calling functions on interface Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface(LIface;)V + + const-string v0, "Calling functions on interface Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface2(LIface2;)V + + return-void +.end method + +.method public static callIface(LIface;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-conflicting function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method + +.method public static callIface2(LIface2;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling conflicting function on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface2;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + + return-void +.end method + +.method public static callMain(LMain;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Unexpected AME Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Expected ICCE Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-conflicting function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/967-default-ame/build b/test/967-default-ame/build new file mode 100755 index 0000000000..53001a9ad2 --- /dev/null +++ b/test/967-default-ame/build @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p src + mkdir -p classes + ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src + # Build with the non-conflicting version + ${JAVAC} -implicit:none -d classes src/Iface.java build-src/Iface2.java build-src/Iface3.java src/Main.java + rm classes/Iface2.class + rm classes/Iface3.class + # Build with the conflicting version + ${JAVAC} -implicit:none -cp classes -d classes src/Iface2.java src/Iface3.java +else + ./default-build "$@" --experimental default-methods +fi diff --git a/test/967-default-ame/build-src/Iface2.java b/test/967-default-ame/build-src/Iface2.java new file mode 100644 index 0000000000..55b2ac01b0 --- /dev/null +++ b/test/967-default-ame/build-src/Iface2.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 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 interface Iface2 extends Iface { + // public String sayHi(); +} + + diff --git a/test/967-default-ame/build-src/Iface3.java b/test/967-default-ame/build-src/Iface3.java new file mode 100644 index 0000000000..a6faa451e5 --- /dev/null +++ b/test/967-default-ame/build-src/Iface3.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 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 interface Iface3 { + // public String charge(); +} diff --git a/test/967-default-ame/expected.txt b/test/967-default-ame/expected.txt new file mode 100644 index 0000000000..cbd4ad32eb --- /dev/null +++ b/test/967-default-ame/expected.txt @@ -0,0 +1,18 @@ +Create Main instance +Calling functions on concrete Main +Calling non-abstract function on Main +CHARGE +Calling abstract function on Main +Expected AME Thrown on Main +Calling non-abstract function on Main +CHARGE +Calling functions on interface Iface +Calling non-abstract function on Iface +CHARGE +Calling abstract function on Iface +Expected AME Thrown on Iface +Calling non-abstract function on Iface +CHARGE +Calling functions on interface Iface2 +Calling abstract function on Iface2 +Expected AME Thrown on Iface2 diff --git a/test/967-default-ame/info.txt b/test/967-default-ame/info.txt new file mode 100644 index 0000000000..a346a322ec --- /dev/null +++ b/test/967-default-ame/info.txt @@ -0,0 +1,6 @@ +Smali-based tests for experimental interface static methods. + +Tests handling of default method overrides. + +To run with --jvm you must export JAVA_HOME to a Java 8 Language installation +and pass the --use-java-home to run-test diff --git a/test/967-default-ame/run b/test/967-default-ame/run new file mode 100755 index 0000000000..8944ea92d3 --- /dev/null +++ b/test/967-default-ame/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/967-default-ame/smali/Iface.smali b/test/967-default-ame/smali/Iface.smali new file mode 100644 index 0000000000..e996b3a4f4 --- /dev/null +++ b/test/967-default-ame/smali/Iface.smali @@ -0,0 +1,39 @@ +# /* +# * Copyright (C) 2015 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 interface Iface { +# public default String sayHi() { +# return "Hi"; +# } +# public default String charge() { +# return "CHARGE"; +# } +# } + +.class public abstract interface LIface; +.super Ljava/lang/Object; + +.method public sayHi()Ljava/lang/String; + .locals 1 + const-string v0, "Hi" + return-object v0 +.end method + +.method public charge()Ljava/lang/String; + .locals 1 + const-string v0, "CHARGE" + return-object v0 +.end method diff --git a/test/967-default-ame/smali/Iface2.smali b/test/967-default-ame/smali/Iface2.smali new file mode 100644 index 0000000000..a21a8ddbc7 --- /dev/null +++ b/test/967-default-ame/smali/Iface2.smali @@ -0,0 +1,27 @@ +# /* +# * Copyright (C) 2015 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 interface Iface2 extends Iface { +# public String sayHi(); +# } + +.class public abstract interface LIface2; +.super Ljava/lang/Object; +.implements LIface; + +.method public abstract sayHi()Ljava/lang/String; +.end method + diff --git a/test/967-default-ame/smali/Iface3.smali b/test/967-default-ame/smali/Iface3.smali new file mode 100644 index 0000000000..874e96d069 --- /dev/null +++ b/test/967-default-ame/smali/Iface3.smali @@ -0,0 +1,26 @@ +# /* +# * Copyright (C) 2015 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 interface Iface3 { +# public String charge(); +# } + +.class public abstract interface LIface3; +.super Ljava/lang/Object; + +.method public abstract charge()Ljava/lang/String; +.end method + diff --git a/test/967-default-ame/smali/Main.smali b/test/967-default-ame/smali/Main.smali new file mode 100644 index 0000000000..e4d63cfa24 --- /dev/null +++ b/test/967-default-ame/smali/Main.smali @@ -0,0 +1,228 @@ +# /* +# * Copyright (C) 2015 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. +# */ +# +# class Main implements Iface, Iface2, Iface3 { +# public static void main(String[] args) { +# System.out.println("Create Main instance"); +# Main m = new Main(); +# System.out.println("Calling functions on concrete Main"); +# callMain(m); +# System.out.println("Calling functions on interface Iface"); +# callIface(m); +# System.out.println("Calling functions on interface Iface2"); +# callIface2(m); +# } +# +# public static void callMain(Main m) { +# System.out.println("Calling non-abstract function on Main"); +# System.out.println(m.charge()); +# System.out.println("Calling abstract function on Main"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Main"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Main"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Main"); +# } +# System.out.println("Calling non-abstract function on Main"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface(Iface m) { +# System.out.println("Calling non-abstract function on Iface"); +# System.out.println(m.charge()); +# System.out.println("Calling abstract function on Iface"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Iface"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Iface"); +# } +# System.out.println("Calling non-abstract function on Iface"); +# System.out.println(m.charge()); +# return; +# } +# +# public static void callIface2(Iface2 m) { +# System.out.println("Calling abstract function on Iface2"); +# try { +# System.out.println(m.sayHi()); +# System.out.println("Unexpected no error Thrown on Iface2"); +# } catch (AbstractMethodError e) { +# System.out.println("Expected AME Thrown on Iface2"); +# } catch (IncompatibleClassChangeError e) { +# System.out.println("Unexpected ICCE Thrown on Iface2"); +# } +# return; +# } +# } + +.class public LMain; +.super Ljava/lang/Object; +.implements LIface; +.implements LIface2; +.implements LIface3; + +.method public constructor <init>()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .locals 3 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v0, "Create Main instance" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + new-instance v2, LMain; + invoke-direct {v2}, LMain;-><init>()V + + const-string v0, "Calling functions on concrete Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callMain(LMain;)V + + const-string v0, "Calling functions on interface Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface(LIface;)V + + const-string v0, "Calling functions on interface Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-static {v2}, LMain;->callIface2(LIface2;)V + + return-void +.end method + +.method public static callIface(LIface;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-abstract function on Iface" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-interface {p0}, LIface;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method + +.method public static callIface2(LIface2;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling abstract function on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-interface {p0}, LIface2;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Iface2" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + + return-void +.end method + +.method public static callMain(LMain;)V + .locals 2 + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + const-string v0, "Calling non-abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Calling abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + :try_start + invoke-virtual {p0}, LMain;->sayHi()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + const-string v0, "Unexpected no error Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + goto :error_end + :try_end + .catch Ljava/lang/AbstractMethodError; {:try_start .. :try_end} :AME_error_start + .catch Ljava/lang/IncompatibleClassChangeError; {:try_start .. :try_end} :ICCE_error_start + :AME_error_start + const-string v0, "Expected AME Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :ICCE_error_start + const-string v0, "Unexpected ICCE Thrown on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + goto :error_end + :error_end + const-string v0, "Calling non-abstract function on Main" + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + invoke-virtual {p0}, LMain;->charge()Ljava/lang/String; + move-result-object v0 + invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + + return-void +.end method diff --git a/test/968-default-partial-compile-generated/build b/test/968-default-partial-compile-generated/build new file mode 100755 index 0000000000..1e9f8aadd5 --- /dev/null +++ b/test/968-default-partial-compile-generated/build @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Copyright 2015 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. + +# make us exit on a failure +set -e + +# We will be making more files than the ulimit is set to allow. Remove it temporarily. +OLD_ULIMIT=`ulimit -S` +ulimit -S unlimited + +restore_ulimit() { + ulimit -S "$OLD_ULIMIT" +} +trap 'restore_ulimit' ERR + +# TODO: Support running with jack. + +if [[ $@ == *"--jvm"* ]]; then + # Build the Java files if we are running a --jvm test + mkdir -p classes + mkdir -p src + echo "${JAVAC} \$@" >> ./javac_exec.sh + # This will use java_exec.sh to execute the javac compiler. It will place the + # compiled class files in ./classes and the expected values in expected.txt + # + # After this the src directory will contain the final versions of all files. + ./util-src/generate_java.py ./javac_exec.sh ./src ./classes ./expected.txt ./build_log +else + mkdir -p ./smali + # Generate the smali files and expected.txt or fail + ./util-src/generate_smali.py ./smali ./expected.txt + # Use the default build script + ./default-build "$@" "$EXTRA_ARGS" --experimental default-methods +fi + +# Reset the ulimit back to its initial value +restore_ulimit diff --git a/test/968-default-partial-compile-generated/expected.txt b/test/968-default-partial-compile-generated/expected.txt new file mode 100644 index 0000000000..1ddd65d177 --- /dev/null +++ b/test/968-default-partial-compile-generated/expected.txt @@ -0,0 +1 @@ +This file is generated by util-src/generate_smali.py do not directly modify! diff --git a/test/968-default-partial-compile-generated/info.txt b/test/968-default-partial-compile-generated/info.txt new file mode 100644 index 0000000000..bc1c42816e --- /dev/null +++ b/test/968-default-partial-compile-generated/info.txt @@ -0,0 +1,17 @@ +Smali-based tests for experimental interface default methods. + +This tests that interface method resolution order is correct in the presence of +partial compilation/illegal invokes. + +Obviously needs to run under ART or a Java 8 Language runtime and compiler. + +When run smali test files are generated by the util-src/generate_smali.py +script. If we run with --jvm we will use the util-src/generate_java.py script +will generate equivalent java code based on the smali code. + +Care should be taken when updating the generate_smali.py script. It should always +return equivalent output when run multiple times and the expected output should +be valid. + +Do not modify the expected.txt file. It is generated on each run by +util-src/generate_smali.py. diff --git a/test/968-default-partial-compile-generated/run b/test/968-default-partial-compile-generated/run new file mode 100755 index 0000000000..6d2930d463 --- /dev/null +++ b/test/968-default-partial-compile-generated/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2015 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. + +${RUN} "$@" --experimental default-methods diff --git a/test/968-default-partial-compile-generated/util-src/generate_java.py b/test/968-default-partial-compile-generated/util-src/generate_java.py new file mode 100755 index 0000000000..35290efe1d --- /dev/null +++ b/test/968-default-partial-compile-generated/util-src/generate_java.py @@ -0,0 +1,134 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 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. + +""" +Generate java test files for test 966. +""" + +import generate_smali as base +import os +import sys +from pathlib import Path + +BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") +if BUILD_TOP is None: + print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) + sys.exit(1) + +# Allow us to import mixins. +sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) + +import testgen.mixins as mixins +import functools +import operator +import subprocess + +class JavaConverter(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin): + """ + A class that can convert a SmaliFile to a JavaFile. + """ + def __init__(self, inner): + self.inner = inner + + def get_name(self): + """Gets the name of this file.""" + return self.inner.get_name() + + def __str__(self): + out = "" + for line in str(self.inner).splitlines(keepends = True): + if line.startswith("#"): + out += line[1:] + return out + +class Compiler: + def __init__(self, sources, javac, temp_dir, classes_dir): + self.javac = javac + self.temp_dir = temp_dir + self.classes_dir = classes_dir + self.sources = sources + + def compile_files(self, args, files): + """ + Compile the files given with the arguments given. + """ + args = args.split() + files = list(map(str, files)) + cmd = ['sh', '-a', '-e', '--', str(self.javac)] + args + files + print("Running compile command: {}".format(cmd)) + subprocess.check_call(cmd) + print("Compiled {} files".format(len(files))) + + def execute(self): + """ + Compiles this test, doing partial compilation as necessary. + """ + # Compile Main and all classes first. Force all interfaces to be default so that there will be + # no compiler problems (works since classes only implement 1 interface). + for f in self.sources: + if isinstance(f, base.TestInterface): + JavaConverter(f.get_specific_version(base.InterfaceType.default)).dump(self.temp_dir) + else: + JavaConverter(f).dump(self.temp_dir) + self.compile_files("-d {}".format(self.classes_dir), self.temp_dir.glob("*.java")) + + # Now we compile the interfaces + ifaces = set(i for i in self.sources if isinstance(i, base.TestInterface)) + while len(ifaces) != 0: + # Find those ifaces where there are no (uncompiled) interfaces that are subtypes. + tops = set(filter(lambda a: not any(map(lambda i: a in i.get_super_types(), ifaces)), ifaces)) + files = [] + # Dump these ones, they are getting compiled. + for f in tops: + out = JavaConverter(f) + out.dump(self.temp_dir) + files.append(self.temp_dir / out.get_file_name()) + # Force all superinterfaces of these to be empty so there will be no conflicts + overrides = functools.reduce(operator.or_, map(lambda i: i.get_super_types(), tops), set()) + for overridden in overrides: + out = JavaConverter(overridden.get_specific_version(base.InterfaceType.empty)) + out.dump(self.temp_dir) + files.append(self.temp_dir / out.get_file_name()) + self.compile_files("-d {outdir} -cp {outdir}".format(outdir = self.classes_dir), files) + # Remove these from the set of interfaces to be compiled. + ifaces -= tops + print("Finished compiling all files.") + return + +def main(argv): + javac_exec = Path(argv[1]) + if not javac_exec.exists() or not javac_exec.is_file(): + print("{} is not a shell script".format(javac_exec), file=sys.stderr) + sys.exit(1) + temp_dir = Path(argv[2]) + if not temp_dir.exists() or not temp_dir.is_dir(): + print("{} is not a valid source dir".format(temp_dir), file=sys.stderr) + sys.exit(1) + classes_dir = Path(argv[3]) + if not classes_dir.exists() or not classes_dir.is_dir(): + print("{} is not a valid classes directory".format(classes_dir), file=sys.stderr) + sys.exit(1) + expected_txt = Path(argv[4]) + mainclass, all_files = base.create_all_test_files() + + with expected_txt.open('w') as out: + print(mainclass.get_expected(), file=out) + print("Wrote expected output") + + Compiler(all_files, javac_exec, temp_dir, classes_dir).execute() + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/968-default-partial-compile-generated/util-src/generate_smali.py b/test/968-default-partial-compile-generated/util-src/generate_smali.py new file mode 100755 index 0000000000..9855bcf854 --- /dev/null +++ b/test/968-default-partial-compile-generated/util-src/generate_smali.py @@ -0,0 +1,607 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2015 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. + +""" +Generate Smali test files for test 967. +""" + +import os +import sys +from pathlib import Path + +BUILD_TOP = os.getenv("ANDROID_BUILD_TOP") +if BUILD_TOP is None: + print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr) + sys.exit(1) + +# Allow us to import utils and mixins. +sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python")) + +from testgen.utils import get_copyright, subtree_sizes, gensym, filter_blanks +import testgen.mixins as mixins + +from enum import Enum +from functools import total_ordering +import itertools +import string + +# The max depth the type tree can have. +MAX_IFACE_DEPTH = 3 + +class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin): + """ + A Main.smali file containing the Main class and the main function. It will run + all the test functions we have. + """ + + MAIN_CLASS_TEMPLATE = """{copyright} + +.class public LMain; +.super Ljava/lang/Object; + +# class Main {{ + +.method public constructor <init>()V + .registers 1 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +{test_funcs} + +{main_func} + +# }} +""" + + MAIN_FUNCTION_TEMPLATE = """ +# public static void main(String[] args) {{ +.method public static main([Ljava/lang/String;)V + .locals 0 + + {test_group_invoke} + + return-void +.end method +# }} +""" + + TEST_GROUP_INVOKE_TEMPLATE = """ +# {test_name}(); + invoke-static {{}}, {test_name}()V +""" + + def __init__(self): + """ + Initialize this MainClass. We start out with no tests. + """ + self.tests = set() + + def get_expected(self): + """ + Get the expected output of this test. + """ + all_tests = sorted(self.tests) + return filter_blanks("\n".join(a.get_expected() for a in all_tests)) + + def add_test(self, ty): + """ + Add a test for the concrete type 'ty' + """ + self.tests.add(Func(ty)) + + def get_name(self): + """ + Get the name of this class + """ + return "Main" + + def __str__(self): + """ + Print the MainClass smali code. + """ + all_tests = sorted(self.tests) + test_invoke = "" + test_funcs = "" + for t in all_tests: + test_funcs += str(t) + for t in all_tests: + test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name()) + main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke) + + return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright("smali"), + test_funcs = test_funcs, + main_func = main_func) + +class Func(mixins.Named, mixins.NameComparableMixin): + """ + A function that tests the functionality of a concrete type. Should only be + constructed by MainClass.add_test. + """ + + TEST_FUNCTION_TEMPLATE = """ +# public static void {fname}() {{ +# {farg} v = null; +# try {{ +# v = new {farg}(); +# }} catch (Throwable e) {{ +# System.out.println("Unexpected error occurred which creating {farg} instance"); +# e.printStackTrace(System.out); +# return; +# }} +# try {{ +# System.out.printf("{tree} calls %s\\n", v.getName()); +# return; +# }} catch (AbstractMethodError e) {{ +# System.out.println("{tree} threw AbstractMethodError"); +# }} catch (NoSuchMethodError e) {{ +# System.out.println("{tree} threw NoSuchMethodError"); +# }} catch (IncompatibleClassChangeError e) {{ +# System.out.println("{tree} threw IncompatibleClassChangeError"); +# }} catch (Throwable e) {{ +# e.printStackTrace(System.out); +# return; +# }} +# }} +.method public static {fname}()V + .locals 7 + sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream; + + :new_{fname}_try_start + new-instance v0, L{farg}; + invoke-direct {{v0}}, L{farg};-><init>()V + goto :call_{fname}_try_start + :new_{fname}_try_end + .catch Ljava/lang/Throwable; {{:new_{fname}_try_start .. :new_{fname}_try_end}} :new_error_{fname}_start + :new_error_{fname}_start + move-exception v6 + const-string v5, "Unexpected error occurred which creating {farg} instance" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + return-void + :call_{fname}_try_start + const/4 v1, 1 + new-array v2,v1, [Ljava/lang/Object; + const/4 v1, 0 + invoke-virtual {{v0}}, L{farg};->getName()Ljava/lang/String; + move-result-object v3 + aput-object v3,v2,v1 + + const-string v5, "{tree} calls %s\\n" + + invoke-virtual {{v4,v5,v2}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; + return-void + :call_{fname}_try_end + .catch Ljava/lang/AbstractMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :AME_{fname}_start + .catch Ljava/lang/NoSuchMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :NSME_{fname}_start + .catch Ljava/lang/IncompatibleClassChangeError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :ICCE_{fname}_start + .catch Ljava/lang/Throwable; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start + :AME_{fname}_start + const-string v5, "{tree} threw AbstractMethodError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :NSME_{fname}_start + const-string v5, "{tree} threw NoSuchMethodError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :ICCE_{fname}_start + const-string v5, "{tree} threw IncompatibleClassChangeError" + invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V + return-void + :error_{fname}_start + move-exception v6 + invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V + return-void +.end method +""" + + NSME_RESULT_TEMPLATE = "{tree} threw NoSuchMethodError" + ICCE_RESULT_TEMPLATE = "{tree} threw IncompatibleClassChangeError" + AME_RESULT_TEMPLATE = "{tree} threw AbstractMethodError" + NORMAL_RESULT_TEMPLATE = "{tree} calls {result}" + + def __init__(self, farg): + """ + Initialize a test function for the given argument + """ + self.farg = farg + + def get_expected(self): + """ + Get the expected output calling this function. + """ + exp = self.farg.get_called() + if exp.is_empty(): + return self.NSME_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + elif exp.is_abstract(): + return self.AME_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + elif exp.is_conflict(): + return self.ICCE_RESULT_TEMPLATE.format(tree = self.farg.get_tree()) + else: + assert exp.is_default() + return self.NORMAL_RESULT_TEMPLATE.format(tree = self.farg.get_tree(), + result = exp.get_tree()) + + def get_name(self): + """ + Get the name of this function + """ + return "TEST_FUNC_{}".format(self.farg.get_name()) + + def __str__(self): + """ + Print the smali code of this function. + """ + return self.TEST_FUNCTION_TEMPLATE.format(tree = self.farg.get_tree(), + fname = self.get_name(), + farg = self.farg.get_name()) + +class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin): + """ + A class that will be instantiated to test default method resolution order. + """ + + TEST_CLASS_TEMPLATE = """{copyright} + +.class public L{class_name}; +.super Ljava/lang/Object; +.implements L{iface_name}; + +# public class {class_name} implements {iface_name} {{ + +.method public constructor <init>()V + .registers 1 + invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V + return-void +.end method + +{funcs} + +# }} +""" + + def __init__(self, iface): + """ + Initialize this test class which implements the given interface + """ + self.iface = iface + self.class_name = "CLASS_"+gensym() + + def get_name(self): + """ + Get the name of this class + """ + return self.class_name + + def get_tree(self): + """ + Print out a representation of the type tree of this class + """ + return "[{class_name} {iface_tree}]".format(class_name = self.class_name, + iface_tree = self.iface.get_tree()) + + def __iter__(self): + """ + Step through all interfaces implemented transitively by this class + """ + yield self.iface + yield from self.iface + + def get_called(self): + """ + Returns the interface that will be called when the method on this class is invoked or + CONFLICT_TYPE if there is no interface that will be called. + """ + return self.iface.get_called() + + def __str__(self): + """ + Print the smali code of this class. + """ + return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'), + iface_name = self.iface.get_name(), + tree = self.get_tree(), + class_name = self.class_name, + funcs = "") + +class InterfaceType(Enum): + """ + An enumeration of all the different types of interfaces we can have. + + default: It has a default method + abstract: It has a method declared but not defined + empty: It does not have the method + """ + default = 0 + abstract = 1 + empty = 2 + + def get_suffix(self): + if self == InterfaceType.default: + return "_DEFAULT" + elif self == InterfaceType.abstract: + return "_ABSTRACT" + elif self == InterfaceType.empty: + return "_EMPTY" + else: + raise TypeError("Interface type had illegal value.") + +class ConflictInterface: + """ + A singleton representing a conflict of default methods. + """ + + def is_conflict(self): + """ + Returns true if this is a conflict interface and calling the method on this interface will + result in an IncompatibleClassChangeError. + """ + return True + + def is_abstract(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in an AbstractMethodError. + """ + return False + + def is_empty(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in a NoSuchMethodError. + """ + return False + + def is_default(self): + """ + Returns true if this is a default interface and calling the method on this interface will + result in a method actually being called. + """ + return False + +CONFLICT_TYPE = ConflictInterface() + +class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin): + """ + An interface that will be used to test default method resolution order. + """ + + TEST_INTERFACE_TEMPLATE = """{copyright} +.class public abstract interface L{class_name}; +.super Ljava/lang/Object; +{implements_spec} + +# public interface {class_name} {extends} {ifaces} {{ + +{funcs} + +# }} +""" + + DEFAULT_FUNC_TEMPLATE = """ +# public default String getName() {{ +# return "{tree}"; +# }} +.method public getName()Ljava/lang/String; + .locals 1 + const-string v0, "{tree}" + return-object v0 +.end method +""" + + ABSTRACT_FUNC_TEMPLATE = """ +# public String getName(); +.method public abstract getName()Ljava/lang/String; +.end method +""" + + EMPTY_FUNC_TEMPLATE = """""" + + IMPLEMENTS_TEMPLATE = """ +.implements L{iface_name}; +""" + + def __init__(self, ifaces, iface_type, full_name = None): + """ + Initialize interface with the given super-interfaces + """ + self.ifaces = sorted(ifaces) + self.iface_type = iface_type + if full_name is None: + end = self.iface_type.get_suffix() + self.class_name = "INTERFACE_"+gensym()+end + else: + self.class_name = full_name + + def get_specific_version(self, v): + """ + Returns a copy of this interface of the given type for use in partial compilation. + """ + return TestInterface(self.ifaces, v, full_name = self.class_name) + + def get_super_types(self): + """ + Returns a set of all the supertypes of this interface + """ + return set(i2 for i2 in self) + + def is_conflict(self): + """ + Returns true if this is a conflict interface and calling the method on this interface will + result in an IncompatibleClassChangeError. + """ + return False + + def is_abstract(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in an AbstractMethodError. + """ + return self.iface_type == InterfaceType.abstract + + def is_empty(self): + """ + Returns true if this is an abstract interface and calling the method on this interface will + result in a NoSuchMethodError. + """ + return self.iface_type == InterfaceType.empty + + def is_default(self): + """ + Returns true if this is a default interface and calling the method on this interface will + result in a method actually being called. + """ + return self.iface_type == InterfaceType.default + + def get_called(self): + """ + Returns the interface that will be called when the method on this class is invoked or + CONFLICT_TYPE if there is no interface that will be called. + """ + if not self.is_empty() or len(self.ifaces) == 0: + return self + else: + best = self + for super_iface in self.ifaces: + super_best = super_iface.get_called() + if super_best.is_conflict(): + return CONFLICT_TYPE + elif best.is_default(): + if super_best.is_default(): + return CONFLICT_TYPE + elif best.is_abstract(): + if super_best.is_default(): + best = super_best + else: + assert best.is_empty() + best = super_best + return best + + def get_name(self): + """ + Get the name of this class + """ + return self.class_name + + def get_tree(self): + """ + Print out a representation of the type tree of this class + """ + return "[{class_name} {iftree}]".format(class_name = self.get_name(), + iftree = print_tree(self.ifaces)) + + def __iter__(self): + """ + Performs depth-first traversal of the interface tree this interface is the + root of. Does not filter out repeats. + """ + for i in self.ifaces: + yield i + yield from i + + def __str__(self): + """ + Print the smali code of this interface. + """ + s_ifaces = " " + j_ifaces = " " + for i in self.ifaces: + s_ifaces += self.IMPLEMENTS_TEMPLATE.format(iface_name = i.get_name()) + j_ifaces += " {},".format(i.get_name()) + j_ifaces = j_ifaces[0:-1] + if self.is_default(): + funcs = self.DEFAULT_FUNC_TEMPLATE.format(tree = self.get_tree()) + elif self.is_abstract(): + funcs = self.ABSTRACT_FUNC_TEMPLATE.format() + else: + funcs = "" + return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'), + implements_spec = s_ifaces, + extends = "extends" if len(self.ifaces) else "", + ifaces = j_ifaces, + funcs = funcs, + tree = self.get_tree(), + class_name = self.class_name) + +def print_tree(ifaces): + """ + Prints a list of iface trees + """ + return " ".join(i.get_tree() for i in ifaces) + +# The deduplicated output of subtree_sizes for each size up to +# MAX_LEAF_IFACE_PER_OBJECT. +SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i)) + for i in range(MAX_IFACE_DEPTH + 1)] + +def create_test_classes(): + """ + Yield all the test classes with the different interface trees + """ + for num in range(1, MAX_IFACE_DEPTH + 1): + for iface in create_interface_trees(num): + yield TestClass(iface) + +def create_interface_trees(num): + """ + Yield all the interface trees up to 'num' depth. + """ + if num == 0: + for iftype in InterfaceType: + yield TestInterface(tuple(), iftype) + return + for split in SUBTREES[num]: + ifaces = [] + for sub in split: + ifaces.append(list(create_interface_trees(sub))) + yield TestInterface(tuple(), InterfaceType.default) + for supers in itertools.product(*ifaces): + for iftype in InterfaceType: + if iftype == InterfaceType.default: + # We can just stop at defaults. We have other tests that a default can override an + # abstract and this cuts down on the number of cases significantly, improving speed of + # this test. + continue + yield TestInterface(supers, iftype) + +def create_all_test_files(): + """ + Creates all the objects representing the files in this test. They just need to + be dumped. + """ + mc = MainClass() + classes = {mc} + for clazz in create_test_classes(): + classes.add(clazz) + for i in clazz: + classes.add(i) + mc.add_test(clazz) + return mc, classes + +def main(argv): + smali_dir = Path(argv[1]) + if not smali_dir.exists() or not smali_dir.is_dir(): + print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr) + sys.exit(1) + expected_txt = Path(argv[2]) + mainclass, all_files = create_all_test_files() + with expected_txt.open('w') as out: + print(mainclass.get_expected(), file=out) + for f in all_files: + f.dump(smali_dir) + +if __name__ == '__main__': + main(sys.argv) diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 8744674a30..b5287216ce 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -226,6 +226,7 @@ TEST_ART_PYTHON3_DEPENDENCY_RUN_TESTS := \ 960-default-smali \ 961-default-iface-resolution-generated \ 964-default-iface-init-generated \ + 968-default-partial-compile-generated # Check if we have python3 to run our tests. ifeq ($(wildcard /usr/bin/python3),) diff --git a/test/utils/python/testgen/mixins.py b/test/utils/python/testgen/mixins.py index 085e51def2..aa8943baf3 100644 --- a/test/utils/python/testgen/mixins.py +++ b/test/utils/python/testgen/mixins.py @@ -79,6 +79,12 @@ class SmaliFileMixin(get_file_extension_mixin(".smali")): """ pass +class JavaFileMixin(get_file_extension_mixin(".java")): + """ + A mixin that defines that the file this class belongs to is get_name() + ".java". + """ + pass + class NameComparableMixin(object): """ A mixin that defines the object comparison and related functionality in terms diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt index 9a8b462740..a5476f7c41 100644 --- a/tools/libcore_failures.txt +++ b/tools/libcore_failures.txt @@ -164,5 +164,11 @@ names: ["libcore.io.OsTest#test_byteBufferPositions_sendto_recvfrom_af_inet6", "libcore.io.OsTest#test_sendtoSocketAddress_af_inet6"], bug: 25178637 +}, +{ + description: "Non-deterministic test because of a dependency on weak ref collection.", + result: EXEC_FAILED, + names: ["org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet"], + bug: 25437292 } ] |