Merge "Optimizing/X86: PC-relative dex cache array addressing."
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc
index 8eb37cf..0355f11 100644
--- a/compiler/dex/verified_method.cc
+++ b/compiler/dex/verified_method.cc
@@ -313,8 +313,9 @@
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 14ba81d..10841e6 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -329,7 +329,7 @@
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 @@
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 aa5e411..bf3a865 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1552,7 +1552,7 @@
*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 @@
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 90db7eb..7326233 100644
--- a/compiler/elf_writer_debug.cc
+++ b/compiler/elf_writer_debug.cc
@@ -451,12 +451,12 @@
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 a38e1f5..6df1527 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -181,7 +181,7 @@
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 0c85323..3f18d9a 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -372,9 +372,9 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
<< " 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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 120de97..a0a785e 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -308,8 +308,11 @@
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 @@
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 @@
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 @@
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 3f2271e..40a3f14 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -899,7 +899,7 @@
// 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 6d05293..54c6cc8 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -2694,7 +2694,7 @@
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 @@
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 @@
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 @@
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 @@
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 (second.IsRegister()) {
+ Register temp = locations->GetTemp(0).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);
+ 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 b36a042..9b78dec 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -2977,7 +2977,7 @@
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 58c6e0f..ac3162f 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -119,9 +119,12 @@
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 d5d6c21..0147b01 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -3024,7 +3024,7 @@
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 9754043..02e5dab 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -123,20 +123,21 @@
}
// 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::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 0d7c796..5814d75 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -163,12 +163,12 @@
}
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 @@
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 @@
// 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 @@
// 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 4111671..2b77901 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 @@
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 @@
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 @@
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 @@
} 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 353881e..0363f20 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -148,7 +148,7 @@
// 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 @@
&type_propagation,
&sharpening,
&simplify,
- &dce,
&fold,
+ &dce,
};
for (size_t i = 0; i < arraysize(optimizations); ++i) {
@@ -534,6 +534,7 @@
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 @@
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 83f53d7..73a44ee 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -362,7 +362,11 @@
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 @@
// 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 @@
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 @@
} 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 @@
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 @@
// 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 @@
}
}
- // 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// (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 4c4d0f2..2878ac9 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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 6e85a82..2be0680 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -405,20 +405,9 @@
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 @@
// 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 659da06..ecc085b 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -99,17 +99,9 @@
}
}
-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() {
+ // 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 5493601..5c05592 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -56,6 +56,8 @@
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 ef22c81..d399bc2 100644
--- a/compiler/optimizing/register_allocator.cc
+++ b/compiler/optimizing/register_allocator.cc
@@ -1525,7 +1525,7 @@
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 @@
// 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 @@
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 4565590..5190eb3 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -660,8 +660,7 @@
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 92ed58c..68cf6d9 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1357,7 +1357,7 @@
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 @@
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 304d4e5..5e71053 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -814,7 +814,7 @@
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 @@
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 82bc8b9..0d6a8c9 100644
--- a/imgdiag/imgdiag_test.cc
+++ b/imgdiag/imgdiag_test.cc
@@ -42,7 +42,7 @@
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 a163380..5a060af 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2374,7 +2374,7 @@
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 88622cc..c587f68 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -177,18 +177,21 @@
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 @@
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 95f0ccb..5fd8969 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 @@
// 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 dbb546d..f7ed812 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -67,6 +67,18 @@
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 @@
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 201b3e6..5a2d6c3 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -136,6 +136,19 @@
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 @@
}
// 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 71afa0f..771b2d0 100644
--- a/runtime/base/arena_allocator.cc
+++ b/runtime/base/arena_allocator.cc
@@ -316,22 +316,22 @@
}
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 ace6c38..36334c4 100644
--- a/runtime/base/arena_allocator.h
+++ b/runtime/base/arena_allocator.h
@@ -288,6 +288,11 @@
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 31f96e4..90c6ee3 100644
--- a/runtime/base/scoped_arena_allocator.cc
+++ b/runtime/base/scoped_arena_allocator.cc
@@ -91,16 +91,19 @@
}
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 a30c73d..a87153b 100644
--- a/runtime/base/scoped_arena_allocator.h
+++ b/runtime/base/scoped_arena_allocator.h
@@ -42,6 +42,7 @@
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 @@
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 5dac95d..f649972 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -303,7 +303,7 @@
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 @@
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 @@
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::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 @@
// 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::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 @@
// 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 @@
// 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 @@
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 @@
}
}
}
- 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 @@
// 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 @@
}
}
}
- dex_cache_image_class_lookup_required_ = false;
}
class MoveClassTableToPreZygoteVisitor : public ClassLoaderVisitor {
@@ -2767,9 +2781,10 @@
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 @@
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 @@
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 @@
// 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);
+ 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;
+ }
- 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();
+ // 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;
}
- 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;
}
}
+ // 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 @@
// 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 @@
}
}
} 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
<< " 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 @@
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 @@
// 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 @@
}
// 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 @@
// 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 @@
// 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 @@
current_method->GetInterfaceMethodIfProxy(image_pointer_size))) {
*imt_ref = current_method;
} else {
- *imt_ref = conflict_method;
+ *imt_ref = imt_conflict_method;
}
}
}
@@ -5080,10 +5245,23 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
//
// 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 @@
"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,
- 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()) {
- // 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;
- } 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);
- }
- 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;
- }
- }
- 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;
+ 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;
}
}
+ }
+ // 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 = vtable_impl;
+ } else {
+ // 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!";
+ }
+ }
+ 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 @@
}
}
}
- 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 @@
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 @@
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 @@
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::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::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 a35ba3e..21f9e7b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -487,10 +487,17 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
// * 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 @@
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 @@
// 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 de692d1..d68b463 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -242,6 +242,15 @@
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 2402e6f..2a0934f 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -120,6 +120,9 @@
__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 3a93aac..70096f5 100644
--- a/runtime/dex_file.cc
+++ b/runtime/dex_file.cc
@@ -231,7 +231,14 @@
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 e7877b2..1e44f50 100644
--- a/runtime/dex_file.h
+++ b/runtime/dex_file.h
@@ -722,6 +722,7 @@
//
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 723ee74..2819670 100644
--- a/runtime/elf_file.cc
+++ b/runtime/elf_file.cc
@@ -189,8 +189,14 @@
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 @@
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 @@
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 @@
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 @@
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 5eda6d6..abf9ac4 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -645,8 +645,8 @@
// 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 1361f7b..8f7bb94 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -487,7 +487,7 @@
// 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 4a49712..f4cf3ae 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 @@
// 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 ab93142..da9a79e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -233,7 +233,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 cc48172..e23b1a3 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -580,9 +580,9 @@
// 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 @@
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 @@
// 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 ce64b10..1fe9a03 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -709,19 +709,32 @@
}
// 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 0bc9dd8..bc2c197 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -108,7 +108,7 @@
}
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 @@
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::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 f4658d5..e2e4782 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -187,7 +187,7 @@
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 7c0594a..d686f74 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -262,7 +262,7 @@
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 @@
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 c8650c4..9f6699f 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -141,8 +141,9 @@
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 @@
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 @@
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 df6936b..f1f4a03 100644
--- a/runtime/jdwp/jdwp_handler.cc
+++ b/runtime/jdwp/jdwp_handler.cc
@@ -745,6 +745,15 @@
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 @@
{ 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 2d3581d..4b2ac20 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -252,9 +252,13 @@
}
#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 @@
}
#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 @@
// 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 @@
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 @@
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 @@
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 7c11ceb..a67a925 100644
--- a/runtime/mem_map.h
+++ b/runtime/mem_map.h
@@ -61,8 +61,13 @@
// 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 @@
// "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 @@
// 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 @@
}
// 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 @@
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 @@
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 13bf5b7..edcbcf2 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 @@
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 @@
&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 @@
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 91e1cec..3590586 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -726,12 +726,12 @@
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 116cbe9..9946eab 100644
--- a/runtime/modifiers.h
+++ b/runtime/modifiers.h
@@ -50,6 +50,10 @@
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 da21fee..19c71f6 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -485,10 +485,7 @@
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 @@
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 @@
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 99080f6..0f3a013 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -846,7 +846,7 @@
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 @@
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 c54d7f8..8c7efb2 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -223,7 +223,7 @@
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 9eee156..ea6d3ff 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -79,11 +79,8 @@
}
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 556ba56..17f34c0 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -528,13 +528,13 @@
// 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 @@
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 @@
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 aae0317..364b8ce 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -52,7 +52,7 @@
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 @@
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,8 +149,91 @@
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);
+}
+
+template <bool kDirect>
+static bool HasNextMethod(ClassDataItemIterator* it) {
+ return kDirect ? it->HasNextDirectMethod() : it->HasNextVirtualMethod();
+}
+
+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_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_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();
+ } else {
+ 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,
+ log_hard_failures,
+ need_precise_constants,
+ &hard_failure_msg);
+ if (result != kNoFailure) {
+ if (result == kHardFailure) {
+ if (*error_count > 0) {
+ *error_string += "\n";
+ }
+ if (!*hard_fail) {
+ *error_string += "Verifier rejected class ";
+ *error_string += PrettyDescriptor(dex_file->GetClassDescriptor(*class_def));
+ *error_string += ":";
+ }
+ *error_string += " ";
+ *error_string += hard_failure_msg;
+ *hard_fail = true;
+ }
+ *error_count = *error_count + 1;
+ }
+ it->Next();
+ }
}
MethodVerifier::FailureKind MethodVerifier::VerifyClass(Thread* self,
@@ -170,6 +242,7 @@
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);
@@ -193,94 +266,35 @@
size_t error_count = 0;
bool hard_fail = false;
ClassLinker* linker = Runtime::Current()->GetClassLinker();
- int64_t previous_direct_method_idx = -1;
- while (it.HasNextDirectMethod()) {
- self->AllowThreadSuspension();
- uint32_t method_idx = it.GetMemberIndex();
- if (method_idx == previous_direct_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_direct_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();
- } else {
- DCHECK(method->GetDeclaringClassUnchecked() != nullptr) << type;
- }
- 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";
- }
- *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";
- }
- *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();
- }
+ // 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
} else {
ArenaUniquePtr<RegisterLine> copy;
- if (gDebugVerify) {
+ if (kDebugVerify) {
copy.reset(RegisterLine::Create(target_line->NumRegs(), this));
copy->CopyFromLine(target_line);
}
@@ -4671,7 +4695,7 @@
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 7b51d6e..719f0d7 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -139,14 +139,20 @@
};
/* 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 @@
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 @@
// 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 @@
* (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 2ab6b4a..c4123d5 100644
--- a/runtime/verifier/method_verifier_test.cc
+++ b/runtime/verifier/method_verifier_test.cc
@@ -37,7 +37,7 @@
// 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 3e533ad..b6b1c43 100644
--- a/test/117-nopatchoat/nopatchoat.cc
+++ b/test/117-nopatchoat/nopatchoat.cc
@@ -35,7 +35,7 @@
}
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 78f8842..7762b2d 100644
--- a/test/137-cfi/cfi.cc
+++ b/test/137-cfi/cfi.cc
@@ -92,7 +92,7 @@
// 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 59e7282..43bc9d0 100644
--- a/test/442-checker-constant-folding/src/Main.java
+++ b/test/442-checker-constant-folding/src/Main.java
@@ -659,6 +659,33 @@
/**
+ * 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 @@
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 f1885de..accf70b 100644
--- a/test/450-checker-types/src/Main.java
+++ b/test/450-checker-types/src/Main.java
@@ -454,7 +454,7 @@
/// 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 @@
/// 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 @@
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 @@
}
}
- /// 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 @@
}
}
- /// 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 @@
/// 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 @@
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 @@
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 8fd7bb7..44facfc 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 979c4c8..12f0380 100644
--- a/test/538-checker-embed-constants/src/Main.java
+++ b/test/538-checker-embed-constants/src/Main.java
@@ -260,6 +260,179 @@
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 @@
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 0000000..e69de29
--- /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 0000000..e541938
--- /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 0000000..44e907d
--- /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 0000000..6e73d0d
--- /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 0000000..e69de29
--- /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 0000000..3255d6b
--- /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 0000000..38fdcc0
--- /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 3946de3..4dc848c 100755
--- a/test/960-default-smali/build
+++ b/test/960-default-smali/build
@@ -20,18 +20,19 @@
# 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 03cc624..b4ced3e 100755
--- a/test/961-default-iface-resolution-generated/build
+++ b/test/961-default-iface-resolution-generated/build
@@ -31,18 +31,19 @@
# 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 24e2feb..e17272f 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 24e2feb..e17272f 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 d916f1b..0780da1 100755
--- a/test/964-default-iface-init-generated/build
+++ b/test/964-default-iface-init-generated/build
@@ -29,18 +29,19 @@
# 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 0000000..5ba5438
--- /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 0000000..300aeec
--- /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 0000000..b31314f
--- /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 0000000..2ccabf5
--- /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 0000000..8944ea9
--- /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 0000000..74799a6
--- /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 0000000..8e90706
--- /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 0000000..1e8cac0
--- /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 0000000..e66e840
--- /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 0000000..8d97df8
--- /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 0000000..fad2c25
--- /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 0000000..2b67657
--- /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 0000000..8944ea9
--- /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 0000000..e996b3a
--- /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 0000000..82fa547
--- /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 0000000..ce974d8
--- /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 0000000..53001a9
--- /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 0000000..55b2ac0
--- /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 0000000..a6faa45
--- /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 0000000..cbd4ad3
--- /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 0000000..a346a32
--- /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 0000000..8944ea9
--- /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 0000000..e996b3a
--- /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 0000000..a21a8dd
--- /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 0000000..874e96d
--- /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 0000000..e4d63cf
--- /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 0000000..1e9f8aa
--- /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 0000000..1ddd65d
--- /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 0000000..bc1c428
--- /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 0000000..6d2930d
--- /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 0000000..35290ef
--- /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 0000000..9855bcf
--- /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 8744674..b528721 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -226,6 +226,7 @@
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 085e51d..aa8943b 100644
--- a/test/utils/python/testgen/mixins.py
+++ b/test/utils/python/testgen/mixins.py
@@ -79,6 +79,12 @@
"""
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 9a8b462..a5476f7 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
}
]