Merge "Don't use dlopen on host for already loaded oat files."
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index be720ad..eaeacc5 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -437,6 +437,9 @@
continue;
}
const DexFile* dex_file = dex_cache->GetDexFile();
+ CHECK(dex_file_oat_index_map_.find(dex_file) != dex_file_oat_index_map_.end())
+ << "Dex cache should have been pruned " << dex_file->GetLocation()
+ << "; possibly in class path";
DexCacheArraysLayout layout(target_ptr_size_, dex_file);
DCHECK(layout.Valid());
size_t oat_index = GetOatIndexForDexCache(dex_cache);
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index 5b19284..21e198c 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -448,23 +448,23 @@
}
TEST_F(OatTest, OatHeaderIsValid) {
- InstructionSet insn_set = kX86;
- std::string error_msg;
- std::unique_ptr<const InstructionSetFeatures> insn_features(
- InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg));
- ASSERT_TRUE(insn_features.get() != nullptr) << error_msg;
- std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set,
- insn_features.get(),
- 0u,
- nullptr));
- ASSERT_NE(oat_header.get(), nullptr);
- ASSERT_TRUE(oat_header->IsValid());
+ InstructionSet insn_set = kX86;
+ std::string error_msg;
+ std::unique_ptr<const InstructionSetFeatures> insn_features(
+ InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg));
+ ASSERT_TRUE(insn_features.get() != nullptr) << error_msg;
+ std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set,
+ insn_features.get(),
+ 0u,
+ nullptr));
+ ASSERT_NE(oat_header.get(), nullptr);
+ ASSERT_TRUE(oat_header->IsValid());
- char* magic = const_cast<char*>(oat_header->GetMagic());
- strcpy(magic, ""); // bad magic
- ASSERT_FALSE(oat_header->IsValid());
- strcpy(magic, "oat\n000"); // bad version
- ASSERT_FALSE(oat_header->IsValid());
+ char* magic = const_cast<char*>(oat_header->GetMagic());
+ strcpy(magic, ""); // bad magic
+ ASSERT_FALSE(oat_header->IsValid());
+ strcpy(magic, "oat\n000"); // bad version
+ ASSERT_FALSE(oat_header->IsValid());
}
TEST_F(OatTest, EmptyTextSection) {
@@ -766,4 +766,28 @@
TestZipFileInput(true);
}
+TEST_F(OatTest, UpdateChecksum) {
+ InstructionSet insn_set = kX86;
+ std::string error_msg;
+ std::unique_ptr<const InstructionSetFeatures> insn_features(
+ InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg));
+ ASSERT_TRUE(insn_features.get() != nullptr) << error_msg;
+ std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set,
+ insn_features.get(),
+ 0u,
+ nullptr));
+ // The starting adler32 value is 1.
+ EXPECT_EQ(1U, oat_header->GetChecksum());
+
+ oat_header->UpdateChecksum(OatHeader::kOatMagic, sizeof(OatHeader::kOatMagic));
+ EXPECT_EQ(64291151U, oat_header->GetChecksum());
+
+ // Make sure that null data does not reset the checksum.
+ oat_header->UpdateChecksum(nullptr, 0);
+ EXPECT_EQ(64291151U, oat_header->GetChecksum());
+
+ oat_header->UpdateChecksum(OatHeader::kOatMagic, sizeof(OatHeader::kOatMagic));
+ EXPECT_EQ(216138397U, oat_header->GetChecksum());
+}
+
} // namespace art
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 140db0c..4232002 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -63,6 +63,29 @@
return reinterpret_cast<const UnalignedDexFileHeader*>(raw_data);
}
+class ChecksumUpdatingOutputStream : public OutputStream {
+ public:
+ ChecksumUpdatingOutputStream(OutputStream* out, OatHeader* oat_header)
+ : OutputStream(out->GetLocation()), out_(out), oat_header_(oat_header) { }
+
+ bool WriteFully(const void* buffer, size_t byte_count) OVERRIDE {
+ oat_header_->UpdateChecksum(buffer, byte_count);
+ return out_->WriteFully(buffer, byte_count);
+ }
+
+ off_t Seek(off_t offset, Whence whence) OVERRIDE {
+ return out_->Seek(offset, whence);
+ }
+
+ bool Flush() OVERRIDE {
+ return out_->Flush();
+ }
+
+ private:
+ OutputStream* const out_;
+ OatHeader* const oat_header_;
+};
+
} // anonymous namespace
// Defines the location of the raw dex file to write.
@@ -422,13 +445,21 @@
for (OatDexFile& oat_dex_file : oat_dex_files_) {
oat_dex_file.ReserveClassOffsets(this);
}
- if (!WriteOatDexFiles(rodata) ||
+ ChecksumUpdatingOutputStream checksum_updating_rodata(rodata, oat_header_.get());
+ if (!WriteOatDexFiles(&checksum_updating_rodata) ||
!ExtendForTypeLookupTables(rodata, file, size_after_type_lookup_tables) ||
!OpenDexFiles(file, verify, &dex_files_map, &dex_files) ||
!WriteTypeLookupTables(dex_files_map.get(), dex_files)) {
return false;
}
+ // Do a bulk checksum update for Dex[] and TypeLookupTable[]. Doing it piece by
+ // piece would be difficult because we're not using the OutpuStream directly.
+ if (!oat_dex_files_.empty()) {
+ size_t size = size_after_type_lookup_tables - oat_dex_files_[0].dex_file_offset_;
+ oat_header_->UpdateChecksum(dex_files_map->Begin(), size);
+ }
+
*opened_dex_files_map = std::move(dex_files_map);
*opened_dex_files = std::move(dex_files);
write_state_ = WriteState::kPrepareLayout;
@@ -996,7 +1027,7 @@
<< PrettyMethod(it.GetMemberIndex(), *dex_file_);
const OatQuickMethodHeader& method_header =
oat_class->method_headers_[method_offsets_index_];
- if (!writer_->WriteData(out, &method_header, sizeof(method_header))) {
+ if (!out->WriteFully(&method_header, sizeof(method_header))) {
ReportWriteFailure("method header", it);
return false;
}
@@ -1063,7 +1094,7 @@
}
}
- if (!writer_->WriteData(out, quick_code.data(), code_size)) {
+ if (!out->WriteFully(quick_code.data(), code_size)) {
ReportWriteFailure("method code", it);
return false;
}
@@ -1279,7 +1310,7 @@
size_t map_size = map.size() * sizeof(map[0]);
if (map_offset == offset_) {
// Write deduplicated map (code info for Optimizing or transformation info for dex2dex).
- if (UNLIKELY(!writer_->WriteData(out, map.data(), map_size))) {
+ if (UNLIKELY(!out->WriteFully(map.data(), map_size))) {
ReportWriteFailure(it);
return false;
}
@@ -1457,6 +1488,10 @@
bool OatWriter::WriteRodata(OutputStream* out) {
CHECK(write_state_ == WriteState::kWriteRoData);
+ // Wrap out to update checksum with each write.
+ ChecksumUpdatingOutputStream checksum_updating_out(out, oat_header_.get());
+ out = &checksum_updating_out;
+
if (!WriteClassOffsets(out)) {
LOG(ERROR) << "Failed to write class offsets to " << out->GetLocation();
return false;
@@ -1499,6 +1534,10 @@
bool OatWriter::WriteCode(OutputStream* out) {
CHECK(write_state_ == WriteState::kWriteText);
+ // Wrap out to update checksum with each write.
+ ChecksumUpdatingOutputStream checksum_updating_out(out, oat_header_.get());
+ out = &checksum_updating_out;
+
SetMultiOatRelativePatcherAdjustment();
const size_t file_offset = oat_data_offset_;
@@ -1683,7 +1722,7 @@
uint32_t alignment_padding = aligned_offset - relative_offset; \
out->Seek(alignment_padding, kSeekCurrent); \
size_trampoline_alignment_ += alignment_padding; \
- if (!WriteData(out, (field)->data(), (field)->size())) { \
+ if (!out->WriteFully((field)->data(), (field)->size())) { \
PLOG(ERROR) << "Failed to write " # field " to " << out->GetLocation(); \
return false; \
} \
@@ -2200,11 +2239,6 @@
return true;
}
-bool OatWriter::WriteData(OutputStream* out, const void* data, size_t size) {
- oat_header_->UpdateChecksum(data, size);
- return out->WriteFully(data, size);
-}
-
void OatWriter::SetMultiOatRelativePatcherAdjustment() {
DCHECK(dex_files_ != nullptr);
DCHECK(relative_patcher_ != nullptr);
@@ -2274,39 +2308,37 @@
const size_t file_offset = oat_writer->oat_data_offset_;
DCHECK_OFFSET_();
- if (!oat_writer->WriteData(out, &dex_file_location_size_, sizeof(dex_file_location_size_))) {
+ if (!out->WriteFully(&dex_file_location_size_, sizeof(dex_file_location_size_))) {
PLOG(ERROR) << "Failed to write dex file location length to " << out->GetLocation();
return false;
}
oat_writer->size_oat_dex_file_location_size_ += sizeof(dex_file_location_size_);
- if (!oat_writer->WriteData(out, dex_file_location_data_, dex_file_location_size_)) {
+ if (!out->WriteFully(dex_file_location_data_, dex_file_location_size_)) {
PLOG(ERROR) << "Failed to write dex file location data to " << out->GetLocation();
return false;
}
oat_writer->size_oat_dex_file_location_data_ += dex_file_location_size_;
- if (!oat_writer->WriteData(out,
- &dex_file_location_checksum_,
- sizeof(dex_file_location_checksum_))) {
+ if (!out->WriteFully(&dex_file_location_checksum_, sizeof(dex_file_location_checksum_))) {
PLOG(ERROR) << "Failed to write dex file location checksum to " << out->GetLocation();
return false;
}
oat_writer->size_oat_dex_file_location_checksum_ += sizeof(dex_file_location_checksum_);
- if (!oat_writer->WriteData(out, &dex_file_offset_, sizeof(dex_file_offset_))) {
+ if (!out->WriteFully(&dex_file_offset_, sizeof(dex_file_offset_))) {
PLOG(ERROR) << "Failed to write dex file offset to " << out->GetLocation();
return false;
}
oat_writer->size_oat_dex_file_offset_ += sizeof(dex_file_offset_);
- if (!oat_writer->WriteData(out, &class_offsets_offset_, sizeof(class_offsets_offset_))) {
+ if (!out->WriteFully(&class_offsets_offset_, sizeof(class_offsets_offset_))) {
PLOG(ERROR) << "Failed to write class offsets offset to " << out->GetLocation();
return false;
}
oat_writer->size_oat_dex_file_class_offsets_offset_ += sizeof(class_offsets_offset_);
- if (!oat_writer->WriteData(out, &lookup_table_offset_, sizeof(lookup_table_offset_))) {
+ if (!out->WriteFully(&lookup_table_offset_, sizeof(lookup_table_offset_))) {
PLOG(ERROR) << "Failed to write lookup table offset to " << out->GetLocation();
return false;
}
@@ -2316,7 +2348,7 @@
}
bool OatWriter::OatDexFile::WriteClassOffsets(OatWriter* oat_writer, OutputStream* out) {
- if (!oat_writer->WriteData(out, class_offsets_.data(), GetClassOffsetsRawSize())) {
+ if (!out->WriteFully(class_offsets_.data(), GetClassOffsetsRawSize())) {
PLOG(ERROR) << "Failed to write oat class offsets for " << GetLocation()
<< " to " << out->GetLocation();
return false;
@@ -2405,13 +2437,13 @@
OutputStream* out,
const size_t file_offset) const {
DCHECK_OFFSET_();
- if (!oat_writer->WriteData(out, &status_, sizeof(status_))) {
+ if (!out->WriteFully(&status_, sizeof(status_))) {
PLOG(ERROR) << "Failed to write class status to " << out->GetLocation();
return false;
}
oat_writer->size_oat_class_status_ += sizeof(status_);
- if (!oat_writer->WriteData(out, &type_, sizeof(type_))) {
+ if (!out->WriteFully(&type_, sizeof(type_))) {
PLOG(ERROR) << "Failed to write oat class type to " << out->GetLocation();
return false;
}
@@ -2419,20 +2451,20 @@
if (method_bitmap_size_ != 0) {
CHECK_EQ(kOatClassSomeCompiled, type_);
- if (!oat_writer->WriteData(out, &method_bitmap_size_, sizeof(method_bitmap_size_))) {
+ if (!out->WriteFully(&method_bitmap_size_, sizeof(method_bitmap_size_))) {
PLOG(ERROR) << "Failed to write method bitmap size to " << out->GetLocation();
return false;
}
oat_writer->size_oat_class_method_bitmaps_ += sizeof(method_bitmap_size_);
- if (!oat_writer->WriteData(out, method_bitmap_->GetRawStorage(), method_bitmap_size_)) {
+ if (!out->WriteFully(method_bitmap_->GetRawStorage(), method_bitmap_size_)) {
PLOG(ERROR) << "Failed to write method bitmap to " << out->GetLocation();
return false;
}
oat_writer->size_oat_class_method_bitmaps_ += method_bitmap_size_;
}
- if (!oat_writer->WriteData(out, method_offsets_.data(), GetMethodOffsetsRawSize())) {
+ if (!out->WriteFully(method_offsets_.data(), GetMethodOffsetsRawSize())) {
PLOG(ERROR) << "Failed to write method offsets to " << out->GetLocation();
return false;
}
diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h
index 3862798..cc81f39 100644
--- a/compiler/oat_writer.h
+++ b/compiler/oat_writer.h
@@ -271,7 +271,6 @@
bool WriteTypeLookupTables(MemMap* opened_dex_files_map,
const std::vector<std::unique_ptr<const DexFile>>& opened_dex_files);
bool WriteCodeAlignment(OutputStream* out, uint32_t aligned_code_delta);
- bool WriteData(OutputStream* out, const void* data, size_t size);
void SetMultiOatRelativePatcherAdjustment();
enum class WriteState {
diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc
index 97b8839..29f7672 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -1150,17 +1150,22 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ CompareAndBranchIfZero(arg, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ CompareAndBranchIfZero(arg, &return_false);
+ }
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ ldr(temp, Address(str, class_offset));
- __ ldr(temp1, Address(arg, class_offset));
- __ cmp(temp, ShifterOperand(temp1));
- __ b(&return_false, NE);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ ldr(temp, Address(str, class_offset));
+ __ ldr(temp1, Address(arg, class_offset));
+ __ cmp(temp, ShifterOperand(temp1));
+ __ b(&return_false, NE);
+ }
// Load lengths of this and argument strings.
__ ldr(temp, Address(str, count_offset));
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 07d9d87..d776fb4 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -1327,21 +1327,26 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ Cbz(arg, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ Cbz(arg, &return_false);
+ }
// Reference equality check, return true if same reference.
__ Cmp(str, arg);
__ B(&return_true, eq);
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ Ldr(temp, MemOperand(str.X(), class_offset));
- __ Ldr(temp1, MemOperand(arg.X(), class_offset));
- __ Cmp(temp, temp1);
- __ B(&return_false, ne);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ Ldr(temp, MemOperand(str.X(), class_offset));
+ __ Ldr(temp1, MemOperand(arg.X(), class_offset));
+ __ Cmp(temp, temp1);
+ __ B(&return_false, ne);
+ }
// Load lengths of this and argument strings.
__ Ldr(temp, MemOperand(str.X(), count_offset));
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index d66940f..05377f9 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -1319,11 +1319,11 @@
__ j(kEqual, &return_false);
}
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
__ movl(ecx, Address(str, class_offset));
__ cmpl(ecx, Address(arg, class_offset));
__ j(kNotEqual, &return_false);
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 2a86769..67c2f3a 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -1416,17 +1416,22 @@
// Note that the null check must have been done earlier.
DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
- // Check if input is null, return false if it is.
- __ testl(arg, arg);
- __ j(kEqual, &return_false);
+ StringEqualsOptimizations optimizations(invoke);
+ if (!optimizations.GetArgumentNotNull()) {
+ // Check if input is null, return false if it is.
+ __ testl(arg, arg);
+ __ j(kEqual, &return_false);
+ }
- // Instanceof check for the argument by comparing class fields.
- // All string objects must have the same type since String cannot be subclassed.
- // Receiver must be a string object, so its class field is equal to all strings' class fields.
- // If the argument is a string object, its class field must be equal to receiver's class field.
- __ movl(rcx, Address(str, class_offset));
- __ cmpl(rcx, Address(arg, class_offset));
- __ j(kNotEqual, &return_false);
+ if (!optimizations.GetArgumentIsString()) {
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ movl(rcx, Address(str, class_offset));
+ __ cmpl(rcx, Address(arg, class_offset));
+ __ j(kNotEqual, &return_false);
+ }
// Reference equality check, return true if same reference.
__ cmpl(str, arg);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 9f6f453..cce83f3 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1269,6 +1269,21 @@
CHECK(runtime != nullptr);
std::set<DexCacheResolvedClasses> resolved_classes(
profile_compilation_info_->GetResolvedClasses());
+
+ // Filter out class path classes since we don't want to include these in the image.
+ std::unordered_set<std::string> dex_files_locations;
+ for (const DexFile* dex_file : dex_files_) {
+ dex_files_locations.insert(dex_file->GetLocation());
+ }
+ for (auto it = resolved_classes.begin(); it != resolved_classes.end(); ) {
+ if (dex_files_locations.find(it->GetDexLocation()) == dex_files_locations.end()) {
+ VLOG(compiler) << "Removed profile samples for non-app dex file " << it->GetDexLocation();
+ it = resolved_classes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
image_classes_.reset(new std::unordered_set<std::string>(
runtime->GetClassLinker()->GetClassDescriptorsForProfileKeys(resolved_classes)));
VLOG(compiler) << "Loaded " << image_classes_->size()
@@ -2443,6 +2458,7 @@
bool multi_image_;
bool is_host_;
std::string android_root_;
+ // Dex files we are compiling, does not include the class path dex files.
std::vector<const DexFile*> dex_files_;
std::string no_inline_from_string_;
std::vector<jobject> dex_caches_;
diff --git a/runtime/base/histogram-inl.h b/runtime/base/histogram-inl.h
index c7a0ba2..4af47d1 100644
--- a/runtime/base/histogram-inl.h
+++ b/runtime/base/histogram-inl.h
@@ -202,9 +202,13 @@
template <class Value>
inline void Histogram<Value>::PrintMemoryUse(std::ostream &os) const {
- os << Name()
- << ": Avg: " << PrettySize(Mean()) << " Max: "
- << PrettySize(Max()) << " Min: " << PrettySize(Min()) << "\n";
+ os << Name();
+ if (sample_size_ != 0u) {
+ os << ": Avg: " << PrettySize(Mean()) << " Max: "
+ << PrettySize(Max()) << " Min: " << PrettySize(Min()) << "\n";
+ } else {
+ os << ": <no data>\n";
+ }
}
template <class Value>
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index d03b57c..9ac27a1 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1348,7 +1348,8 @@
for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) {
// The image space is not yet added to the heap, avoid read barriers.
mirror::Class* klass = types[j].Read();
- if (klass != nullptr) {
+ // There may also be boot image classes,
+ if (space->HasAddress(klass)) {
DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
// Update the class loader from the one in the image class loader to the one that loaded
// the app image.
@@ -1387,6 +1388,9 @@
VLOG(image) << PrettyMethod(&m);
}
}
+ } else {
+ DCHECK(klass == nullptr || heap->ObjectIsInBootImageSpace(klass))
+ << klass << " " << PrettyClass(klass);
}
}
}
@@ -1394,10 +1398,10 @@
for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) {
// The image space is not yet added to the heap, avoid read barriers.
mirror::Class* klass = types[j].Read();
- if (klass != nullptr) {
+ if (space->HasAddress(klass)) {
DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
if (kIsDebugBuild) {
- if (new_class_set != nullptr) {
+ if (new_class_set != nullptr) {
auto it = new_class_set->Find(GcRoot<mirror::Class>(klass));
DCHECK(it != new_class_set->end());
DCHECK_EQ(it->Read(), klass);
@@ -1662,6 +1666,10 @@
// resolve the same way, simply flatten the hierarchy in the way the resolution order would be,
// and check that the dex file names are the same.
for (mirror::ClassLoader* image_class_loader : image_class_loaders) {
+ if (IsBootClassLoader(soa, image_class_loader)) {
+ // The dex cache can reference types from the boot class loader.
+ continue;
+ }
std::list<mirror::String*> image_dex_file_names;
std::string temp_error_msg;
if (!FlattenPathClassLoader(image_class_loader, &image_dex_file_names, &temp_error_msg)) {
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 8005642..5b54f7d 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -644,8 +644,7 @@
LOG(INFO) << "Debugger is no longer active";
- // Suspend all threads and exclusively acquire the mutator lock. Set the state of the thread
- // to kRunnable to avoid scoped object access transitions. Remove the debugger as a listener
+ // Suspend all threads and exclusively acquire the mutator lock. Remove the debugger as a listener
// and clear the object registry.
Runtime* runtime = Runtime::Current();
Thread* self = Thread::Current();
@@ -655,7 +654,6 @@
gc::kGcCauseInstrumentation,
gc::kCollectorTypeInstrumentation);
ScopedSuspendAll ssa(__FUNCTION__);
- ThreadState old_state = self->SetStateUnsafe(kRunnable);
// Debugger may not be active at this point.
if (IsDebuggerActive()) {
{
@@ -676,7 +674,6 @@
}
gDebuggerActive = false;
}
- CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable);
}
{
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index b0a786e..9822f6e 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -35,6 +35,8 @@
// with all profile savers running at the same time.
static constexpr const uint64_t kMinSavePeriodNs = MsToNs(20 * 1000); // 20 seconds
static constexpr const uint64_t kSaveResolvedClassesDelayMs = 2 * 1000; // 2 seconds
+// Minimum number of JIT samples during launch to include a method into the profile.
+static constexpr const size_t kStartupMethodSamples = 1;
static constexpr const uint32_t kMinimumNumberOfMethodsToSave = 10;
static constexpr const uint32_t kMinimumNumberOfClassesToSave = 10;
@@ -108,7 +110,7 @@
}
total_ms_of_sleep_ += kSaveResolvedClassesDelayMs;
}
- FetchAndCacheResolvedClasses();
+ FetchAndCacheResolvedClassesAndMethods();
// Loop for the profiled methods.
while (!ShuttingDown(self)) {
@@ -118,7 +120,7 @@
{
MutexLock mu(self, wait_lock_);
period_condition_.Wait(self);
- sleep_time = NanoTime() - last_time_ns_saver_woke_up_;
+ sleep_time = NanoTime() - sleep_start;
}
// Check if the thread was woken up for shutdown.
if (ShuttingDown(self)) {
@@ -128,11 +130,11 @@
// We might have been woken up by a huge number of notifications to guarantee saving.
// If we didn't meet the minimum saving period go back to sleep (only if missed by
// a reasonable margin).
- while (kMinSavePeriodNs - sleep_time > (kMinSavePeriodNs / 10)) {
+ while (kMinSavePeriodNs * 0.9 > sleep_time) {
{
MutexLock mu(self, wait_lock_);
period_condition_.TimedWait(self, NsToMs(kMinSavePeriodNs - sleep_time), 0);
- sleep_time = NanoTime() - last_time_ns_saver_woke_up_;
+ sleep_time = NanoTime() - sleep_start;
}
// Check if the thread was woken up for shutdown.
if (ShuttingDown(self)) {
@@ -204,11 +206,48 @@
return &info_it->second;
}
-void ProfileSaver::FetchAndCacheResolvedClasses() {
+// Get resolved methods that have a profile info or more than kStartupMethodSamples samples.
+// Excludes native methods and classes in the boot image.
+class GetMethodsVisitor : public ClassVisitor {
+ public:
+ explicit GetMethodsVisitor(std::vector<MethodReference>* methods) : methods_(methods) {}
+
+ virtual bool operator()(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass)) {
+ return true;
+ }
+ for (ArtMethod& method : klass->GetMethods(sizeof(void*))) {
+ if (!method.IsNative()) {
+ if (method.GetCounter() >= kStartupMethodSamples ||
+ method.GetProfilingInfo(sizeof(void*)) != nullptr) {
+ // Have samples, add to profile.
+ const DexFile* dex_file = method.GetInterfaceMethodIfProxy(sizeof(void*))->GetDexFile();
+ methods_->push_back(MethodReference(dex_file, method.GetDexMethodIndex()));
+ }
+ }
+ }
+ return true;
+ }
+
+ private:
+ std::vector<MethodReference>* const methods_;
+};
+
+void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() {
ScopedTrace trace(__PRETTY_FUNCTION__);
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
std::set<DexCacheResolvedClasses> resolved_classes =
class_linker->GetResolvedClasses(/*ignore boot classes*/ true);
+
+ std::vector<MethodReference> methods;
+ {
+ ScopedTrace trace2("Get hot methods");
+ GetMethodsVisitor visitor(&methods);
+ ScopedObjectAccess soa(Thread::Current());
+ class_linker->VisitClasses(&visitor);
+ VLOG(profiler) << "Methods with samples greater than "
+ << kStartupMethodSamples << " = " << methods.size();
+ }
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
uint64_t total_number_of_profile_entries_cached = 0;
@@ -216,11 +255,16 @@
std::set<DexCacheResolvedClasses> resolved_classes_for_location;
const std::string& filename = it.first;
const std::set<std::string>& locations = it.second;
-
+ std::vector<MethodReference> methods_for_location;
+ for (const MethodReference& ref : methods) {
+ if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) {
+ methods_for_location.push_back(ref);
+ }
+ }
for (const DexCacheResolvedClasses& classes : resolved_classes) {
if (locations.find(classes.GetBaseLocation()) != locations.end()) {
- VLOG(profiler) << "Added classes for location " << classes.GetBaseLocation()
- << " (" << classes.GetDexLocation() << ")";
+ VLOG(profiler) << "Added " << classes.GetClasses().size() << " classes for location "
+ << classes.GetBaseLocation() << " (" << classes.GetDexLocation() << ")";
resolved_classes_for_location.insert(classes);
} else {
VLOG(profiler) << "Location not found " << classes.GetBaseLocation()
@@ -228,7 +272,7 @@
}
}
ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
- info->AddMethodsAndClasses(std::vector<MethodReference>(), resolved_classes_for_location);
+ info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location);
total_number_of_profile_entries_cached += resolved_classes_for_location.size();
}
max_number_of_profile_entries_cached_ = std::max(
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index c6da959..9c6d0fa 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -95,9 +95,9 @@
// If no entry exists, a new empty one will be created, added to the cache and
// then returned.
ProfileCompilationInfo* GetCachedProfiledInfo(const std::string& filename);
- // Fetches the current resolved classes from the ClassLinker and stores them
- // in the profile_cache_ for later save.
- void FetchAndCacheResolvedClasses();
+ // Fetches the current resolved classes and methods from the ClassLinker and stores them in the
+ // profile_cache_ for later save.
+ void FetchAndCacheResolvedClassesAndMethods();
static bool MaybeRecordDexUseInternal(
const std::string& dex_location,
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index dfb728f..fcdfc88 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -419,8 +419,6 @@
}
return false;
}
- DCHECK_EQ(this->CanAccessMember(access_to, method->GetAccessFlags()),
- this->CanAccessMember(dex_access_to, method->GetAccessFlags()));
}
if (LIKELY(this->CanAccessMember(access_to, method->GetAccessFlags()))) {
return true;
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 2da3d84..2894b68 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -148,9 +148,7 @@
for (size_t i = 0, count = NumStrings(); i < count; ++i) {
mirror::String* source = src[i].Read<kReadBarrierOption>();
mirror::String* new_source = visitor(source);
- if (source != new_source) {
- dest[i] = GcRoot<mirror::String>(new_source);
- }
+ dest[i] = GcRoot<mirror::String>(new_source);
}
}
@@ -160,9 +158,7 @@
for (size_t i = 0, count = NumResolvedTypes(); i < count; ++i) {
mirror::Class* source = src[i].Read<kReadBarrierOption>();
mirror::Class* new_source = visitor(source);
- if (source != new_source) {
- dest[i] = GcRoot<mirror::Class>(new_source);
- }
+ dest[i] = GcRoot<mirror::Class>(new_source);
}
}
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 0126b4d..f30f7a6 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -475,15 +475,22 @@
// public API
static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename) {
- const char* instruction_set = GetInstructionSetString(kRuntimeISA);
- ScopedUtfChars filename(env, javaFilename);
- jint status = GetDexOptNeeded(
- env,
- filename.c_str(),
- instruction_set,
- "speed-profile",
- /*profile_changed*/false);
- return (status != OatFileAssistant::kNoDexOptNeeded) ? JNI_TRUE : JNI_FALSE;
+ ScopedUtfChars filename_utf(env, javaFilename);
+ if (env->ExceptionCheck()) {
+ return JNI_FALSE;
+ }
+
+ const char* filename = filename_utf.c_str();
+ if ((filename == nullptr) || !OS::FileExists(filename)) {
+ LOG(ERROR) << "DexFile_isDexOptNeeded file '" << filename << "' does not exist";
+ ScopedLocalRef<jclass> fnfe(env, env->FindClass("java/io/FileNotFoundException"));
+ const char* message = (filename == nullptr) ? "<empty file name>" : filename;
+ env->ThrowNew(fnfe.get(), message);
+ return JNI_FALSE;
+ }
+
+ OatFileAssistant oat_file_assistant(filename, kRuntimeISA, false, false);
+ return oat_file_assistant.IsUpToDate() ? JNI_FALSE : JNI_TRUE;
}
static jboolean DexFile_isValidCompilerFilter(JNIEnv* env,
diff --git a/runtime/oat.cc b/runtime/oat.cc
index 80231f3..aab0e81 100644
--- a/runtime/oat.cc
+++ b/runtime/oat.cc
@@ -182,8 +182,12 @@
void OatHeader::UpdateChecksum(const void* data, size_t length) {
DCHECK(IsValid());
- const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
- adler32_checksum_ = adler32(adler32_checksum_, bytes, length);
+ if (data != nullptr) {
+ const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
+ adler32_checksum_ = adler32(adler32_checksum_, bytes, length);
+ } else {
+ DCHECK_EQ(0U, length);
+ }
}
InstructionSet OatHeader::GetInstructionSet() const {
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 64b40b7..218c490 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -220,6 +220,10 @@
return true;
}
+bool OatFileAssistant::IsUpToDate() {
+ return OatFileIsUpToDate() || OdexFileIsUpToDate();
+}
+
OatFileAssistant::ResultOfAttemptToUpdate
OatFileAssistant::MakeUpToDate(std::string* error_msg) {
CompilerFilter::Filter target;
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index f48cdf3..bb7b408 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -149,6 +149,10 @@
// given compiler filter.
DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter);
+ // Returns true if there is up-to-date code for this dex location,
+ // irrespective of the compiler filter of the up-to-date code.
+ bool IsUpToDate();
+
// Return code used when attempting to generate updated code.
enum ResultOfAttemptToUpdate {
kUpdateFailed, // We tried making the code up to date, but
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index b5fdcbd..d7d2b5c 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -586,23 +586,25 @@
const OatFile* source_oat_file = nullptr;
- // Update the oat file on disk if we can, based on the --compiler-filter
- // option derived from the current runtime options.
- // This may fail, but that's okay. Best effort is all that matters here.
- switch (oat_file_assistant.MakeUpToDate(/*out*/ &error_msg)) {
- case OatFileAssistant::kUpdateFailed:
- LOG(WARNING) << error_msg;
- break;
+ if (!oat_file_assistant.IsUpToDate()) {
+ // Update the oat file on disk if we can, based on the --compiler-filter
+ // option derived from the current runtime options.
+ // This may fail, but that's okay. Best effort is all that matters here.
+ switch (oat_file_assistant.MakeUpToDate(/*out*/ &error_msg)) {
+ case OatFileAssistant::kUpdateFailed:
+ LOG(WARNING) << error_msg;
+ break;
- case OatFileAssistant::kUpdateNotAttempted:
- // Avoid spamming the logs if we decided not to attempt making the oat
- // file up to date.
- VLOG(oat) << error_msg;
- break;
+ case OatFileAssistant::kUpdateNotAttempted:
+ // Avoid spamming the logs if we decided not to attempt making the oat
+ // file up to date.
+ VLOG(oat) << error_msg;
+ break;
- case OatFileAssistant::kUpdateSucceeded:
- // Nothing to do.
- break;
+ case OatFileAssistant::kUpdateSucceeded:
+ // Nothing to do.
+ break;
+ }
}
// Get the oat file on disk.
diff --git a/runtime/thread.h b/runtime/thread.h
index 582a0cd..0591a75 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -1119,7 +1119,7 @@
SHARED_REQUIRES(Locks::mutator_lock_);
// Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit, ~Thread and
- // Dbg::Disconnected.
+ // Dbg::ManageDeoptimization.
ThreadState SetStateUnsafe(ThreadState new_state) {
ThreadState old_state = GetState();
if (old_state == kRunnable && new_state != kRunnable) {
diff --git a/test/536-checker-intrinsic-optimization/src/Main.java b/test/536-checker-intrinsic-optimization/src/Main.java
index 15a9504..24ed2fe 100644
--- a/test/536-checker-intrinsic-optimization/src/Main.java
+++ b/test/536-checker-intrinsic-optimization/src/Main.java
@@ -107,8 +107,28 @@
}
/// CHECK-START-X86: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
- /// CHECK: InvokeVirtual {{.*\.equals.*}}
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
/// CHECK-NOT: test
+
+ /// CHECK-START-X86_64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ /// CHECK-NOT: test
+
+ /// CHECK-START-ARM: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ // CompareAndBranchIfZero() may emit either CBZ or CMP+BEQ.
+ /// CHECK-NOT: cbz
+ /// CHECK-NOT: cmp {{r\d+}}, #0
+ // Terminate the scope for the CHECK-NOT search at the reference or length comparison,
+ // whichever comes first.
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+
+ /// CHECK-START-ARM64: boolean Main.stringArgumentNotNull(java.lang.Object) disassembly (after)
+ /// CHECK: InvokeVirtual {{.*\.equals.*}} intrinsic:StringEquals
+ /// CHECK-NOT: cbz
+ // Terminate the scope for the CHECK-NOT search at the reference or length comparison,
+ // whichever comes first.
+ /// CHECK: cmp {{w.*,}} {{w.*}}
public static boolean stringArgumentNotNull(Object obj) {
obj.getClass();
return "foo".equals(obj);
@@ -116,12 +136,53 @@
// Test is very brittle as it depends on the order we emit instructions.
/// CHECK-START-X86: boolean Main.stringArgumentIsString() disassembly (after)
- /// CHECK: InvokeVirtual
- /// CHECK: test
- /// CHECK: jz/eq
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: test
+ /// CHECK: jz/eq
// Check that we don't try to compare the classes.
- /// CHECK-NOT: mov
- /// CHECK: cmp
+ /// CHECK-NOT: mov
+ /// CHECK: cmp
+
+ // Test is very brittle as it depends on the order we emit instructions.
+ /// CHECK-START-X86_64: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: test
+ /// CHECK: jz/eq
+ // Check that we don't try to compare the classes.
+ /// CHECK-NOT: mov
+ /// CHECK: cmp
+
+ // Test is brittle as it depends on the class offset being 0.
+ /// CHECK-START-ARM: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: {{cbz|cmp}}
+ // Check that we don't try to compare the classes.
+ // The dissassembler currently explicitly emits the offset 0 but don't rely on it.
+ // We want to terminate the CHECK-NOT search after two CMPs, one for reference
+ // equality and one for length comparison but these may be emitted in different order,
+ // so repeat the check twice.
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}]
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0]
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}]
+ /// CHECK-NOT: ldr{{(|.w)}} {{r\d+}}, [{{r\d+}}, #0]
+ /// CHECK: cmp {{r\d+}}, {{r\d+}}
+
+ // Test is brittle as it depends on the class offset being 0.
+ /// CHECK-START-ARM64: boolean Main.stringArgumentIsString() disassembly (after)
+ /// CHECK: InvokeVirtual intrinsic:StringEquals
+ /// CHECK: cbz
+ // Check that we don't try to compare the classes.
+ // The dissassembler currently does not explicitly emits the offset 0 but don't rely on it.
+ // We want to terminate the CHECK-NOT search after two CMPs, one for reference
+ // equality and one for length comparison but these may be emitted in different order,
+ // so repeat the check twice.
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}]
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0]
+ /// CHECK: cmp {{w\d+}}, {{w\d+}}
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}]
+ /// CHECK-NOT: ldr {{w\d+}}, [{{x\d+}}, #0]
+ /// CHECK: cmp {{w\d+}}, {{w\d+}}
public static boolean stringArgumentIsString() {
return "foo".equals(myString);
}
diff --git a/test/601-method-access/expected.txt b/test/601-method-access/expected.txt
new file mode 100644
index 0000000..90fbab8
--- /dev/null
+++ b/test/601-method-access/expected.txt
@@ -0,0 +1 @@
+Got expected failure
diff --git a/test/601-method-access/info.txt b/test/601-method-access/info.txt
new file mode 100644
index 0000000..e38a336
--- /dev/null
+++ b/test/601-method-access/info.txt
@@ -0,0 +1 @@
+Regression test for method access checks.
diff --git a/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali b/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali
new file mode 100644
index 0000000..7a896a2
--- /dev/null
+++ b/test/601-method-access/smali/SubClassUsingInaccessibleMethod.smali
@@ -0,0 +1,33 @@
+# Copyright (C) 2016 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 LSubClassUsingInaccessibleMethod;
+
+.super Lother/PublicClass;
+
+.method public constructor <init>()V
+ .registers 1
+ invoke-direct {p0}, Lother/PublicClass;-><init>()V
+ return-void
+.end method
+
+# Regression test for compiler DCHECK() failure (bogus check) when referencing
+# a package-private method from an indirectly inherited package-private class,
+# using this very class as the declaring class in the MethodId, bug: 28771056.
+.method public test()I
+ .registers 2
+ invoke-virtual {p0}, LSubClassUsingInaccessibleMethod;->otherProtectedClassPackageIntInstanceMethod()I
+ move-result v0
+ return v0
+.end method
diff --git a/test/601-method-access/src/Main.java b/test/601-method-access/src/Main.java
new file mode 100644
index 0000000..838080a
--- /dev/null
+++ b/test/601-method-access/src/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+import java.lang.reflect.InvocationTargetException;
+
+/*
+ * Test method access through reflection.
+ */
+public class Main {
+ public static void main(String[] args) {
+ try {
+ Class c = Class.forName("SubClassUsingInaccessibleMethod");
+ Object o = c.newInstance();
+ c.getMethod("test").invoke(o, null);
+ } catch (InvocationTargetException ite) {
+ if (ite.getCause() instanceof IllegalAccessError) {
+ System.out.println("Got expected failure");
+ } else {
+ System.out.println("Got unexpected failure " + ite.getCause());
+ }
+ } catch (Exception e) {
+ System.out.println("Got unexpected failure " + e);
+ }
+ }
+}
diff --git a/test/601-method-access/src/other/ProtectedClass.java b/test/601-method-access/src/other/ProtectedClass.java
new file mode 100644
index 0000000..9426884
--- /dev/null
+++ b/test/601-method-access/src/other/ProtectedClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package other;
+
+// Class that cannot be accessed outside of this package.
+class ProtectedClass {
+ /* package */ int otherProtectedClassPackageIntInstanceMethod() {
+ return 28;
+ }
+}
diff --git a/test/601-method-access/src/other/PublicClass.java b/test/601-method-access/src/other/PublicClass.java
new file mode 100644
index 0000000..d9f7961
--- /dev/null
+++ b/test/601-method-access/src/other/PublicClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package other;
+
+// Class that makes the ProtectedClass sub-classable by classes outside of package other.
+public class PublicClass extends ProtectedClass {
+}