diff options
| -rw-r--r-- | compiler/driver/compiler_driver.cc | 36 | ||||
| -rw-r--r-- | compiler/image_test.h | 3 | ||||
| -rw-r--r-- | compiler/image_writer.cc | 13 | ||||
| -rw-r--r-- | compiler/image_writer.h | 7 | ||||
| -rw-r--r-- | compiler/verifier_deps_test.cc | 38 | ||||
| -rw-r--r-- | dex2oat/dex2oat.cc | 37 | ||||
| -rw-r--r-- | dex2oat/dex2oat_image_test.cc | 9 | ||||
| -rw-r--r-- | imgdiag/imgdiag.cc | 74 | ||||
| -rw-r--r-- | runtime/aot_class_linker.cc | 22 | ||||
| -rw-r--r-- | runtime/aot_class_linker.h | 7 | ||||
| -rw-r--r-- | runtime/class_linker.cc | 6 | ||||
| -rw-r--r-- | runtime/class_linker.h | 6 | ||||
| -rw-r--r-- | runtime/vdex_file.h | 4 | ||||
| -rw-r--r-- | runtime/verifier/verifier_deps.cc | 20 | ||||
| -rw-r--r-- | runtime/verifier/verifier_deps.h | 4 | ||||
| -rw-r--r-- | test/660-clinit/expected.txt | 1 | ||||
| -rw-r--r-- | test/660-clinit/profile | 5 | ||||
| -rw-r--r-- | test/660-clinit/src/Main.java | 63 | ||||
| -rw-r--r-- | test/906-iterate-heap/expected.txt | 2 | ||||
| -rw-r--r-- | test/906-iterate-heap/src/art/Test906.java | 29 | ||||
| -rw-r--r-- | test/913-heaps/expected.txt | 2 | ||||
| -rw-r--r-- | test/913-heaps/src/art/Test913.java | 29 | ||||
| -rw-r--r-- | test/knownfailures.json | 6 | ||||
| -rw-r--r-- | tools/art | 71 |
24 files changed, 416 insertions, 78 deletions
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 0b1bce62c9..5eee7e003b 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -1935,14 +1935,12 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, // time. So instead we assume these classes still need to be verified at // runtime. for (const DexFile* dex_file : dex_files) { - // Fetch the list of unverified classes and turn it into a set for faster - // lookups. - const std::vector<dex::TypeIndex>& unverified_classes = + // Fetch the list of unverified classes. + const std::set<dex::TypeIndex>& unverified_classes = verifier_deps->GetUnverifiedClasses(*dex_file); - std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end()); for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); - if (set.find(class_def.class_idx_) == set.end()) { + if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) { if (compiler_only_verifies) { // Just update the compiled_classes_ map. The compiler doesn't need to resolve // the type. @@ -2253,6 +2251,7 @@ class InitializeClassVisitor : public CompilationVisitor { const char* descriptor = dex_file.StringDataByIdx(class_type_id.descriptor_idx_); ScopedObjectAccessUnchecked soa(Thread::Current()); StackHandleScope<3> hs(soa.Self()); + ClassLinker *class_linker = manager_->GetClassLinker(); const bool is_boot_image = manager_->GetCompiler()->GetCompilerOptions().IsBootImage(); const bool is_app_image = manager_->GetCompiler()->GetCompilerOptions().IsAppImage(); @@ -2266,7 +2265,7 @@ class InitializeClassVisitor : public CompilationVisitor { if (klass->IsVerified()) { // Attempt to initialize the class but bail if we either need to initialize the super-class // or static fields. - manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, false); + class_linker->EnsureInitialized(soa.Self(), klass, false, false); old_status = klass->GetStatus(); if (!klass->IsInitialized()) { // We don't want non-trivial class initialization occurring on multiple threads due to @@ -2285,7 +2284,7 @@ class InitializeClassVisitor : public CompilationVisitor { bool is_superclass_initialized = !is_app_image ? true : InitializeDependencies(klass, class_loader, soa.Self()); if (!is_app_image || (is_app_image && is_superclass_initialized)) { - manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, false, true); + class_linker->EnsureInitialized(soa.Self(), klass, false, true); } // Otherwise it's in app image but superclasses can't be initialized, no need to proceed. old_status = klass->GetStatus(); @@ -2311,10 +2310,13 @@ class InitializeClassVisitor : public CompilationVisitor { CHECK(is_app_image); // The boot image case doesn't need to recursively initialize the dependencies with // special logic since the class linker already does this. + // Optimization will be disabled in debuggable build, because in debuggable mode we + // want the <clinit> behavior to be observable for the debugger, so we don't do the + // <clinit> at compile time. can_init_static_fields = + !manager_->GetCompiler()->GetCompilerOptions().GetDebuggable() && !soa.Self()->IsExceptionPending() && - is_superclass_initialized && - NoClinitInDependency(klass, soa.Self(), &class_loader); + is_superclass_initialized; // TODO The checking for clinit can be removed since it's already // checked when init superclass. Currently keep it because it contains // processing of intern strings. Will be removed later when intern strings @@ -2328,6 +2330,18 @@ class InitializeClassVisitor : public CompilationVisitor { // a ReaderWriterMutex but we're holding the mutator lock so we fail mutex sanity // checks in Thread::AssertThreadSuspensionIsAllowable. Runtime* const runtime = Runtime::Current(); + // Resolve and initialize the exception type before enabling the transaction in case + // the transaction aborts and cannot resolve the type. + // TransactionAbortError is not initialized ant not in boot image, needed only by + // compiler and will be pruned by ImageWriter. + Handle<mirror::Class> exception_class = + hs.NewHandle(class_linker->FindClass(Thread::Current(), + Transaction::kAbortExceptionSignature, + class_loader)); + bool exception_initialized = + class_linker->EnsureInitialized(soa.Self(), exception_class, true, true); + DCHECK(exception_initialized); + // Run the class initializer in transaction mode. runtime->EnterTransactionMode(is_app_image, klass.Get()); bool success = manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, true, @@ -2365,10 +2379,12 @@ class InitializeClassVisitor : public CompilationVisitor { } } - if (!success) { + if (!success && is_boot_image) { // On failure, still intern strings of static fields and seen in <clinit>, as these // will be created in the zygote. This is separated from the transaction code just // above as we will allocate strings, so must be allowed to suspend. + // We only need to intern strings for boot image because classes that failed to be + // initialized will not appear in app image. if (&klass->GetDexFile() == manager_->GetDexFile()) { InternStrings(klass, class_loader); } else { diff --git a/compiler/image_test.h b/compiler/image_test.h index 57d0987982..daa4b11967 100644 --- a/compiler/image_test.h +++ b/compiler/image_test.h @@ -214,7 +214,8 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, /*compile_app_image*/false, storage_mode, oat_filename_vector, - dex_file_to_oat_index_map)); + dex_file_to_oat_index_map, + /*dirty_image_objects*/nullptr)); { { jobject class_loader = nullptr; diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index f4e8a89e92..9e4971ce75 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -579,7 +579,12 @@ void ImageWriter::AssignImageBinSlot(mirror::Object* object, size_t oat_index) { } } - if (klass->GetStatus() == Class::kStatusInitialized) { + // Move known dirty objects into their own sections. This includes: + // - classes with dirty static fields. + if (dirty_image_objects_ != nullptr && + dirty_image_objects_->find(klass->PrettyDescriptor()) != dirty_image_objects_->end()) { + bin = kBinKnownDirty; + } else if (klass->GetStatus() == Class::kStatusInitialized) { bin = kBinClassInitialized; // If the class's static fields are all final, put it into a separate bin @@ -2774,7 +2779,8 @@ ImageWriter::ImageWriter( bool compile_app_image, ImageHeader::StorageMode image_storage_mode, const std::vector<const char*>& oat_filenames, - const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map) + const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map, + const std::unordered_set<std::string>* dirty_image_objects) : compiler_driver_(compiler_driver), global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)), image_objects_offset_begin_(0), @@ -2786,7 +2792,8 @@ ImageWriter::ImageWriter( clean_methods_(0u), image_storage_mode_(image_storage_mode), oat_filenames_(oat_filenames), - dex_file_oat_index_map_(dex_file_oat_index_map) { + dex_file_oat_index_map_(dex_file_oat_index_map), + dirty_image_objects_(dirty_image_objects) { CHECK_NE(image_begin, 0U); std::fill_n(image_methods_, arraysize(image_methods_), nullptr); CHECK_EQ(compile_app_image, !Runtime::Current()->GetHeap()->GetBootImageSpaces().empty()) diff --git a/compiler/image_writer.h b/compiler/image_writer.h index 34bbbad75d..866e2042f7 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -75,7 +75,8 @@ class ImageWriter FINAL { bool compile_app_image, ImageHeader::StorageMode image_storage_mode, const std::vector<const char*>& oat_filenames, - const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map); + const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map, + const std::unordered_set<std::string>* dirty_image_objects); bool PrepareImageAddressSpace(); @@ -159,6 +160,7 @@ class ImageWriter FINAL { // Classify different kinds of bins that objects end up getting packed into during image writing. // Ordered from dirtiest to cleanest (until ArtMethods). enum Bin { + kBinKnownDirty, // Known dirty objects from --dirty-image-objects list kBinMiscDirty, // Dex caches, object locks, etc... kBinClassVerified, // Class verified, but initializers haven't been run // Unknown mix of clean/dirty: @@ -599,6 +601,9 @@ class ImageWriter FINAL { // Map of dex files to the indexes of oat files that they were compiled into. const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map_; + // Set of objects known to be dirty in the image. Can be nullptr if there are none. + const std::unordered_set<std::string>* dirty_image_objects_; + class ComputeLazyFieldsForClassesVisitor; class FixupClassVisitor; class FixupRootVisitor; diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index e9f3f8022d..65389252e2 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -229,8 +229,7 @@ class VerifierDepsTest : public CommonCompilerTest { hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_))); MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr)); for (const DexFile* dex_file : dex_files_) { - const std::vector<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file); - std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end()); + const std::set<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file); for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); const char* descriptor = dex_file->GetClassDescriptor(class_def); @@ -238,7 +237,7 @@ class VerifierDepsTest : public CommonCompilerTest { if (cls == nullptr) { CHECK(soa.Self()->IsExceptionPending()); soa.Self()->ClearException(); - } else if (set.find(class_def.class_idx_) == set.end()) { + } else if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) { ASSERT_EQ(cls->GetStatus(), mirror::Class::kStatusVerified); } else { ASSERT_LT(cls->GetStatus(), mirror::Class::kStatusVerified); @@ -1145,6 +1144,39 @@ TEST_F(VerifierDepsTest, UnverifiedClasses) { ASSERT_TRUE(HasUnverifiedClass("LMyClassWithNoSuperButFailures;")); } +TEST_F(VerifierDepsTest, UnverifiedOrder) { + ScopedObjectAccess soa(Thread::Current()); + jobject loader = LoadDex("VerifierDeps"); + std::vector<const DexFile*> dex_files = GetDexFiles(loader); + ASSERT_GT(dex_files.size(), 0u); + const DexFile* dex_file = dex_files[0]; + VerifierDeps deps1(dex_files); + Thread* const self = Thread::Current(); + ASSERT_TRUE(self->GetVerifierDeps() == nullptr); + self->SetVerifierDeps(&deps1); + deps1.MaybeRecordVerificationStatus(*dex_file, + dex::TypeIndex(0), + verifier::FailureKind::kHardFailure); + deps1.MaybeRecordVerificationStatus(*dex_file, + dex::TypeIndex(1), + verifier::FailureKind::kHardFailure); + VerifierDeps deps2(dex_files); + self->SetVerifierDeps(nullptr); + self->SetVerifierDeps(&deps2); + deps2.MaybeRecordVerificationStatus(*dex_file, + dex::TypeIndex(1), + verifier::FailureKind::kHardFailure); + deps2.MaybeRecordVerificationStatus(*dex_file, + dex::TypeIndex(0), + verifier::FailureKind::kHardFailure); + self->SetVerifierDeps(nullptr); + std::vector<uint8_t> buffer1; + deps1.Encode(dex_files, &buffer1); + std::vector<uint8_t> buffer2; + deps2.Encode(dex_files, &buffer2); + EXPECT_EQ(buffer1, buffer2); +} + TEST_F(VerifierDepsTest, VerifyDeps) { VerifyDexFile(); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 0826fa1488..d57bf6cab3 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -408,17 +408,17 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(""); UsageError(" --class-loader-context=<string spec>: a string specifying the intended"); UsageError(" runtime loading context for the compiled dex files."); - UsageError(" "); + UsageError(""); UsageError(" It describes how the class loader chain should be built in order to ensure"); UsageError(" classes are resolved during dex2aot as they would be resolved at runtime."); UsageError(" This spec will be encoded in the oat file. If at runtime the dex file is"); UsageError(" loaded in a different context, the oat file will be rejected."); - UsageError(" "); + UsageError(""); UsageError(" The chain is interpreted in the natural 'parent order', meaning that class"); UsageError(" loader 'i+1' will be the parent of class loader 'i'."); UsageError(" The compilation sources will be appended to the classpath of the first class"); UsageError(" loader."); - UsageError(" "); + UsageError(""); UsageError(" E.g. if the context is 'PCL[lib1.dex];DLC[lib2.dex]' and "); UsageError(" --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath "); UsageError(" 'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with "); @@ -428,9 +428,12 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" with --dex-file are found in the classpath. The source dex files will be"); UsageError(" removed from any class loader's classpath possibly resulting in empty"); UsageError(" class loaders."); - UsageError(" "); + UsageError(""); UsageError(" Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]"); UsageError(""); + UsageError(" --dirty-image-objects=<directory-path>: list of known dirty objects in the image."); + UsageError(" The image writer will group them together."); + UsageError(""); std::cerr << "See log for usage error information\n"; exit(EXIT_FAILURE); } @@ -1307,6 +1310,8 @@ class Dex2Oat FINAL { if (class_loader_context_== nullptr) { Usage("Option --class-loader-context has an incorrect format: %s", option.data()); } + } else if (option.starts_with("--dirty-image-objects=")) { + dirty_image_objects_filename_ = option.substr(strlen("--dirty-image-objects=")).data(); } else if (!compiler_options_->ParseCompilerOption(option, Usage)) { Usage("Unknown argument %s", option.data()); } @@ -1508,7 +1513,8 @@ class Dex2Oat FINAL { dex2oat::ReturnCode Setup() { TimingLogger::ScopedTiming t("dex2oat Setup", timings_); - if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods()) { + if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods() || + !PrepareDirtyObjects()) { return dex2oat::ReturnCode::kOther; } @@ -2002,7 +2008,8 @@ class Dex2Oat FINAL { IsAppImage(), image_storage_mode_, oat_filenames_, - dex_file_oat_index_map_)); + dex_file_oat_index_map_, + dirty_image_objects_.get())); // We need to prepare method offsets in the image address space for direct method patching. TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_); @@ -2428,6 +2435,22 @@ class Dex2Oat FINAL { return true; } + bool PrepareDirtyObjects() { + if (dirty_image_objects_filename_ != nullptr) { + dirty_image_objects_.reset(ReadCommentedInputFromFile<std::unordered_set<std::string>>( + dirty_image_objects_filename_, + nullptr)); + if (dirty_image_objects_ == nullptr) { + LOG(ERROR) << "Failed to create list of dirty objects from '" + << dirty_image_objects_filename_ << "'"; + return false; + } + } else { + dirty_image_objects_.reset(nullptr); + } + return true; + } + void PruneNonExistentDexFiles() { DCHECK_EQ(dex_filenames_.size(), dex_locations_.size()); size_t kept = 0u; @@ -2845,9 +2868,11 @@ class Dex2Oat FINAL { const char* compiled_methods_zip_filename_; const char* compiled_methods_filename_; const char* passes_to_run_filename_; + const char* dirty_image_objects_filename_; std::unique_ptr<std::unordered_set<std::string>> image_classes_; std::unique_ptr<std::unordered_set<std::string>> compiled_classes_; std::unique_ptr<std::unordered_set<std::string>> compiled_methods_; + std::unique_ptr<std::unordered_set<std::string>> dirty_image_objects_; std::unique_ptr<std::vector<std::string>> passes_to_run_; bool multi_image_; bool is_host_; diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc index 95fb16dfd7..46c5f581b6 100644 --- a/dex2oat/dex2oat_image_test.cc +++ b/dex2oat/dex2oat_image_test.cc @@ -340,6 +340,15 @@ TEST_F(Dex2oatImageTest, TestModesAndFilters) { // EXPECT_GE(profile_sizes.oat_size / kRatio, compiled_methods_sizes.oat_size); EXPECT_GE(profile_sizes.vdex_size / kRatio, compiled_methods_sizes.vdex_size); } + // Test dirty image objects. + { + ScratchFile classes; + GenerateClasses(classes.GetFile(), /*frequency*/ 1u); + image_classes_sizes = CompileImageAndGetSizes( + {"--dirty-image-objects=" + classes.GetFilename()}); + classes.Close(); + std::cout << "Dirty image object sizes " << image_classes_sizes << std::endl; + } } } // namespace art diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index 7ef24c700e..9ffc4149ab 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -333,9 +333,11 @@ class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object std::vector<uint8_t>* remote_contents, std::vector<uint8_t>* zygote_contents, const backtrace_map_t& boot_map, - const ImageHeader& image_header) : - RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header), - os_(*os) { } + const ImageHeader& image_header, + bool dump_dirty_objects) + : RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header), + os_(*os), + dump_dirty_objects_(dump_dirty_objects) { } void CheckEntrySanity(const uint8_t* current) const REQUIRES_SHARED(Locks::mutator_lock_) { @@ -396,7 +398,10 @@ class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object class_data_[klass].AddDirtyObject(entry, entry_remote); } - void DiffEntryContents(mirror::Object* entry, uint8_t* remote_bytes, const uint8_t* base_ptr) + void DiffEntryContents(mirror::Object* entry, + uint8_t* remote_bytes, + const uint8_t* base_ptr, + bool log_dirty_objects) REQUIRES_SHARED(Locks::mutator_lock_) { const char* tabs = " "; // Attempt to find fields for all dirty bytes. @@ -453,6 +458,9 @@ class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object } } if (!dirty_static_fields.empty()) { + if (dump_dirty_objects_ && log_dirty_objects) { + dirty_objects_.insert(entry); + } os_ << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n"; for (ArtField* field : dirty_static_fields) { os_ << tabs << ArtField::PrettyField(field) @@ -463,6 +471,14 @@ class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object os_ << "\n"; } + void DumpDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_) { + for (mirror::Object* obj : dirty_objects_) { + if (obj->IsClass()) { + os_ << "Private dirty object: " << obj->AsClass()->PrettyDescriptor() << "\n"; + } + } + } + void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) { // vector of pairs (size_t count, Class*) auto dirty_object_class_values = @@ -592,6 +608,8 @@ class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object }; std::ostream& os_; + bool dump_dirty_objects_; + std::unordered_set<mirror::Object*> dirty_objects_; std::map<mirror::Class*, ClassData> class_data_; DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase); @@ -720,9 +738,15 @@ class RegionData : public RegionSpecializedBase<T> { std::vector<uint8_t>* remote_contents, std::vector<uint8_t>* zygote_contents, const backtrace_map_t& boot_map, - const ImageHeader& image_header) : - RegionSpecializedBase<T>(os, remote_contents, zygote_contents, boot_map, image_header), - os_(*os) { + const ImageHeader& image_header, + bool dump_dirty_objects) + : RegionSpecializedBase<T>(os, + remote_contents, + zygote_contents, + boot_map, + image_header, + dump_dirty_objects), + os_(*os) { CHECK(remote_contents != nullptr); CHECK(zygote_contents != nullptr); } @@ -773,7 +797,8 @@ class RegionData : public RegionSpecializedBase<T> { DiffDirtyEntries(ProcessType::kRemote, begin_image_ptr, RegionCommon<T>::remote_contents_, - base_ptr); + base_ptr, + /*log_dirty_objects*/true); // Print shared dirty after since it's less important. if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) { // We only reach this point if both pids were specified. Furthermore, @@ -784,8 +809,10 @@ class RegionData : public RegionSpecializedBase<T> { DiffDirtyEntries(ProcessType::kZygote, begin_image_ptr, RegionCommon<T>::zygote_contents_, - begin_image_ptr); + begin_image_ptr, + /*log_dirty_objects*/false); } + RegionSpecializedBase<T>::DumpDirtyObjects(); RegionSpecializedBase<T>::DumpDirtyEntries(); RegionSpecializedBase<T>::DumpFalseDirtyEntries(); RegionSpecializedBase<T>::DumpCleanEntries(); @@ -797,7 +824,8 @@ class RegionData : public RegionSpecializedBase<T> { void DiffDirtyEntries(ProcessType process_type, const uint8_t* begin_image_ptr, std::vector<uint8_t>* contents, - const uint8_t* base_ptr) + const uint8_t* base_ptr, + bool log_dirty_objects) REQUIRES_SHARED(Locks::mutator_lock_) { os_ << RegionCommon<T>::dirty_entries_.size() << "\n"; const std::set<T*>& entries = @@ -808,7 +836,10 @@ class RegionData : public RegionSpecializedBase<T> { uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry); ptrdiff_t offset = entry_bytes - begin_image_ptr; uint8_t* remote_bytes = &(*contents)[offset]; - RegionSpecializedBase<T>::DiffEntryContents(entry, remote_bytes, &base_ptr[offset]); + RegionSpecializedBase<T>::DiffEntryContents(entry, + remote_bytes, + &base_ptr[offset], + log_dirty_objects); } } @@ -872,12 +903,14 @@ class ImgDiagDumper { const ImageHeader& image_header, const std::string& image_location, pid_t image_diff_pid, - pid_t zygote_diff_pid) + pid_t zygote_diff_pid, + bool dump_dirty_objects) : os_(os), image_header_(image_header), image_location_(image_location), image_diff_pid_(image_diff_pid), zygote_diff_pid_(zygote_diff_pid), + dump_dirty_objects_(dump_dirty_objects), zygote_pid_only_(false) {} bool Init() { @@ -1207,7 +1240,8 @@ class ImgDiagDumper { &remote_contents_, &zygote_contents_, boot_map_, - image_header_); + image_header_, + dump_dirty_objects_); RemoteProcesses remotes; if (zygote_pid_only_) { @@ -1364,6 +1398,7 @@ class ImgDiagDumper { const std::string image_location_; pid_t image_diff_pid_; // Dump image diff against boot.art if pid is non-negative pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative + bool dump_dirty_objects_; // Adds dumping of objects that are dirty. bool zygote_pid_only_; // The user only specified a pid for the zygote. // BacktraceMap used for finding the memory mapping of the image file. @@ -1391,7 +1426,8 @@ class ImgDiagDumper { static int DumpImage(Runtime* runtime, std::ostream* os, pid_t image_diff_pid, - pid_t zygote_diff_pid) { + pid_t zygote_diff_pid, + bool dump_dirty_objects) { ScopedObjectAccess soa(Thread::Current()); gc::Heap* heap = runtime->GetHeap(); std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces(); @@ -1407,7 +1443,8 @@ static int DumpImage(Runtime* runtime, image_header, image_space->GetImageLocation(), image_diff_pid, - zygote_diff_pid); + zygote_diff_pid, + dump_dirty_objects); if (!img_diag_dumper.Init()) { return EXIT_FAILURE; } @@ -1445,6 +1482,8 @@ struct ImgDiagArgs : public CmdlineArgs { *error_msg = "Zygote diff pid out of range"; return kParseError; } + } else if (option == "--dump-dirty-objects") { + dump_dirty_objects_ = true; } else { return kParseUnknownArgument; } @@ -1497,6 +1536,7 @@ struct ImgDiagArgs : public CmdlineArgs { " --zygote-diff-pid=<pid>: provide the PID of the zygote whose boot.art you want to diff " "against.\n" " Example: --zygote-diff-pid=$(pid zygote)\n" + " --dump-dirty-objects: additionally output dirty objects of interest.\n" "\n"; return usage; @@ -1505,6 +1545,7 @@ struct ImgDiagArgs : public CmdlineArgs { public: pid_t image_diff_pid_ = -1; pid_t zygote_diff_pid_ = -1; + bool dump_dirty_objects_ = false; }; struct ImgDiagMain : public CmdlineMain<ImgDiagArgs> { @@ -1514,7 +1555,8 @@ struct ImgDiagMain : public CmdlineMain<ImgDiagArgs> { return DumpImage(runtime, args_->os_, args_->image_diff_pid_, - args_->zygote_diff_pid_) == EXIT_SUCCESS; + args_->zygote_diff_pid_, + args_->dump_dirty_objects_) == EXIT_SUCCESS; } }; diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc index b1bc3f8f2e..d8f9e22962 100644 --- a/runtime/aot_class_linker.cc +++ b/runtime/aot_class_linker.cc @@ -27,6 +27,16 @@ AotClassLinker::AotClassLinker(InternTable *intern_table) : ClassLinker(intern_t AotClassLinker::~AotClassLinker() {} +bool AotClassLinker::CanAllocClass() { + // AllocClass doesn't work under transaction, so we abort. + if (Runtime::Current()->IsActiveTransaction()) { + Runtime::Current()->AbortTransactionAndThrowAbortError(Thread::Current(), "Can't resolve this " + "type within a transaction."); + return false; + } + return ClassLinker::CanAllocClass(); +} + // Wrap the original InitializeClass with creation of transaction when in strict mode. bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, bool can_init_statics, bool can_init_parents) { @@ -38,6 +48,13 @@ bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents); } + // When in strict_mode, don't initialize a class if it belongs to boot but not initialized. + if (strict_mode_ && klass->IsBootStrapClassLoaded()) { + runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve " + + klass->PrettyTypeOf() + " because it is an uninitialized boot class."); + return false; + } + // Don't initialize klass if it's superclass is not initialized, because superclass might abort // the transaction and rolled back after klass's change is commited. if (strict_mode_ && !klass->IsInterface() && klass->HasSuperClass()) { @@ -58,9 +75,8 @@ bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, // Exit Transaction if success. runtime->ExitTransactionMode(); } else { - // If not successfully initialized, the last transaction must abort. Don't rollback - // immediately, leave the cleanup to compiler driver which needs abort message and exception. - DCHECK(runtime->IsTransactionAborted()); + // If not successfully initialized, don't rollback immediately, leave the cleanup to compiler + // driver which needs abort message and exception. DCHECK(self->IsExceptionPending()); } } diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h index 11bea86fc4..e9a96fae20 100644 --- a/runtime/aot_class_linker.h +++ b/runtime/aot_class_linker.h @@ -27,6 +27,13 @@ class AotClassLinker : public ClassLinker { explicit AotClassLinker(InternTable *intern_table); ~AotClassLinker(); + // Override AllocClass because aot compiler will need to perform a transaction check to determine + // can we allocate class from heap. + bool CanAllocClass() + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Roles::uninterruptible_); + bool InitializeClass(Thread *self, Handle<mirror::Class> klass, bool can_run_clinit, diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 3ac87c5137..46b0113cdd 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2854,7 +2854,11 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, // Interface object should get the right size here. Regular class will // figure out the right size later and be replaced with one of the right // size when the class becomes resolved. - klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def))); + if (CanAllocClass()) { + klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def))); + } else { + return nullptr; + } } if (UNLIKELY(klass == nullptr)) { self->AssertPendingOOMException(); diff --git a/runtime/class_linker.h b/runtime/class_linker.h index bf14aebb52..584bd1d5ce 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -710,6 +710,12 @@ class ClassLinker { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_); + virtual bool CanAllocClass() + REQUIRES_SHARED(Locks::mutator_lock_) + REQUIRES(!Locks::dex_lock_) { + return true; + } + private: class LinkInterfaceMethodsHelper; diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index 0351fd3afb..63058cfe6f 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -72,8 +72,8 @@ class VdexFile { private: static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; - // Last update: Change method lookup. - static constexpr uint8_t kVdexVersion[] = { '0', '0', '9', '\0' }; + // Last update: Use set for unverified_classes_. + static constexpr uint8_t kVdexVersion[] = { '0', '1', '0', '\0' }; uint8_t magic_[4]; uint8_t version_[4]; diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc index 470b0b3d58..0481f24c45 100644 --- a/runtime/verifier/verifier_deps.cc +++ b/runtime/verifier/verifier_deps.cc @@ -59,9 +59,7 @@ void VerifierDeps::MergeWith(const VerifierDeps& other, MergeSets(my_deps->classes_, other_deps.classes_); MergeSets(my_deps->fields_, other_deps.fields_); MergeSets(my_deps->methods_, other_deps.methods_); - for (dex::TypeIndex entry : other_deps.unverified_classes_) { - my_deps->unverified_classes_.push_back(entry); - } + MergeSets(my_deps->unverified_classes_, other_deps.unverified_classes_); } } @@ -507,7 +505,7 @@ void VerifierDeps::MaybeRecordVerificationStatus(const DexFile& dex_file, VerifierDeps* thread_deps = GetThreadLocalVerifierDeps(); if (thread_deps != nullptr) { DexFileDeps* dex_deps = thread_deps->GetDexFileDeps(dex_file); - dex_deps->unverified_classes_.push_back(type_idx); + dex_deps->unverified_classes_.insert(type_idx); } } @@ -586,6 +584,16 @@ template<> inline dex::StringIndex Decode<dex::StringIndex>(uint32_t in) { return dex::StringIndex(in); } +// TODO: Clean this up, if we use a template arg here it confuses the compiler. +static inline void EncodeTuple(std::vector<uint8_t>* out, const dex::TypeIndex& t) { + EncodeUnsignedLeb128(out, Encode(t)); +} + +// TODO: Clean this up, if we use a template arg here it confuses the compiler. +static inline void DecodeTuple(const uint8_t** in, const uint8_t* end, dex::TypeIndex* t) { + *t = Decode<dex::TypeIndex>(DecodeUint32WithOverflowCheck(in, end)); +} + template<typename T1, typename T2> static inline void EncodeTuple(std::vector<uint8_t>* out, const std::tuple<T1, T2>& t) { EncodeUnsignedLeb128(out, Encode(std::get<0>(t))); @@ -692,7 +700,7 @@ void VerifierDeps::Encode(const std::vector<const DexFile*>& dex_files, EncodeSet(buffer, deps.classes_); EncodeSet(buffer, deps.fields_); EncodeSet(buffer, deps.methods_); - EncodeUint16Vector(buffer, deps.unverified_classes_); + EncodeSet(buffer, deps.unverified_classes_); } } @@ -715,7 +723,7 @@ VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files, DecodeSet(&data_start, data_end, &deps->classes_); DecodeSet(&data_start, data_end, &deps->fields_); DecodeSet(&data_start, data_end, &deps->methods_); - DecodeUint16Vector(&data_start, data_end, &deps->unverified_classes_); + DecodeSet(&data_start, data_end, &deps->unverified_classes_); } CHECK_LE(data_start, data_end); } diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h index 2d452f6d6b..4069a1188a 100644 --- a/runtime/verifier/verifier_deps.h +++ b/runtime/verifier/verifier_deps.h @@ -117,7 +117,7 @@ class VerifierDeps { bool ValidateDependencies(Handle<mirror::ClassLoader> class_loader, Thread* self) const REQUIRES_SHARED(Locks::mutator_lock_); - const std::vector<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const { + const std::set<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const { return GetDexFileDeps(dex_file)->unverified_classes_; } @@ -197,7 +197,7 @@ class VerifierDeps { std::set<MethodResolution> methods_; // List of classes that were not fully verified in that dex file. - std::vector<dex::TypeIndex> unverified_classes_; + std::set<dex::TypeIndex> unverified_classes_; bool Equals(const DexFileDeps& rhs) const; }; diff --git a/test/660-clinit/expected.txt b/test/660-clinit/expected.txt index 9eb4941276..ee1b47929e 100644 --- a/test/660-clinit/expected.txt +++ b/test/660-clinit/expected.txt @@ -1,4 +1,5 @@ JNI_OnLoad called +hello world A.a: 5 A.a: 10 B.b: 10 diff --git a/test/660-clinit/profile b/test/660-clinit/profile index 0239f22039..9eb4924ab1 100644 --- a/test/660-clinit/profile +++ b/test/660-clinit/profile @@ -4,7 +4,10 @@ LDay; LA; LB; LC; +LE; LG; LGs; LObjectRef; - +LInvokeStatic; +LClinitE; +LPrint; diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java index f9b068e110..51b4d604f5 100644 --- a/test/660-clinit/src/Main.java +++ b/test/660-clinit/src/Main.java @@ -24,19 +24,28 @@ public class Main { if (!checkAppImageLoaded()) { System.out.println("AppImage not loaded."); } + if (!checkAppImageContains(ClInit.class)) { + System.out.println("ClInit class is not in app image!"); + } - expectNotPreInit(Day.class); - expectNotPreInit(ClInit.class); // should pass - expectNotPreInit(A.class); // should pass - expectNotPreInit(B.class); // should fail - expectNotPreInit(C.class); // should fail - expectNotPreInit(G.class); // should fail - expectNotPreInit(Gs.class); // should fail - expectNotPreInit(Gss.class); // should fail + expectPreInit(ClInit.class); + expectPreInit(A.class); + expectPreInit(E.class); + expectNotPreInit(B.class); + expectNotPreInit(C.class); + expectNotPreInit(G.class); + expectNotPreInit(Gs.class); + expectNotPreInit(Gss.class); + expectPreInit(InvokeStatic.class); + expectNotPreInit(ClinitE.class); expectNotPreInit(Add.class); expectNotPreInit(Mul.class); expectNotPreInit(ObjectRef.class); + expectNotPreInit(Print.class); + + Print p = new Print(); + Gs gs = new Gs(); A x = new A(); System.out.println("A.a: " + A.a); @@ -62,6 +71,10 @@ public class Main { System.out.println("a != 101"); } + try { + ClinitE e = new ClinitE(); + } catch (Error err) { } + return; } @@ -154,6 +167,13 @@ class C { } } +class E { + public static final int e; + static { + e = 100; + } +} + class G { static G g; static int i; @@ -182,9 +202,36 @@ class Add { } } +// test of INVOKE_STATIC instruction +class InvokeStatic { + static int a; + static int b; + static { + a = Add.exec(10, 20); + b = Mul.exec(10, 20); + } +} + // non-image class Mul { static int exec(int a, int b) { return a * b; } } + +class ClinitE { + static { + if (Math.sin(3) < 0.5) { + // throw anyway, can't initialized + throw new ExceptionInInitializerError("Can't initialize this class!"); + } + } +} + +// fail because JNI +class Print { + static { + System.out.println("hello world"); + } +} + diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt index 73b7129bba..85391fa25a 100644 --- a/test/906-iterate-heap/expected.txt +++ b/test/906-iterate-heap/expected.txt @@ -19,9 +19,7 @@ 1@0 (32, 2xD '0000000000000000000000000000f03f') 2 doTestPrimitiveFieldsClasses -10000@0 (static, int, index=3) 0000000000000000 10001 -10000@0 (static, int, index=11) 0000000000000000 10001 10001 10001 diff --git a/test/906-iterate-heap/src/art/Test906.java b/test/906-iterate-heap/src/art/Test906.java index 65c2c8c560..1878687e26 100644 --- a/test/906-iterate-heap/src/art/Test906.java +++ b/test/906-iterate-heap/src/art/Test906.java @@ -143,19 +143,40 @@ public class Test906 { private static void doTestPrimitiveFieldsClasses() { System.out.println("doTestPrimitiveFieldsClasses"); + boolean correctHeapValue = false; + setTag(IntObject.class, 10000); - System.out.println(iterateThroughHeapPrimitiveFields(10000)); + String heapTrace = iterateThroughHeapPrimitiveFields(10000); + + if (!checkInitialized(IntObject.class)) { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000000"); + } else { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000005"); + } + + if (!correctHeapValue) + System.out.println("Heap Trace for IntObject is not as expected:\n" + heapTrace); + System.out.println(getTag(IntObject.class)); setTag(IntObject.class, 0); setTag(FloatObject.class, 10000); - System.out.println(iterateThroughHeapPrimitiveFields(10000)); + heapTrace = iterateThroughHeapPrimitiveFields(10000); + + if (!checkInitialized(FloatObject.class)) { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000000"); + } else { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000006"); + } + + if (!correctHeapValue) + System.out.println("Heap Trace for FloatObject is not as expected:\n" + heapTrace); + System.out.println(getTag(FloatObject.class)); setTag(FloatObject.class, 0); - boolean correctHeapValue = false; setTag(Inf1.class, 10000); - String heapTrace = iterateThroughHeapPrimitiveFields(10000); + heapTrace = iterateThroughHeapPrimitiveFields(10000); if (!checkInitialized(Inf1.class)) { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000000"); diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt index 6144881a55..844afe8172 100644 --- a/test/913-heaps/expected.txt +++ b/test/913-heaps/expected.txt @@ -136,9 +136,7 @@ root@root --(thread)--> 3000@0 [size=136, length=-1] 4@0 (18, 3xS '010002000300') 1@0 (14, 2xZ '0001') 23456789 -10000@0 (static, int, index=3) 0000000000000000 10001 -10000@0 (static, int, index=11) 0000000000000000 10001 10001 10001 diff --git a/test/913-heaps/src/art/Test913.java b/test/913-heaps/src/art/Test913.java index b9990010ff..28f9546d95 100644 --- a/test/913-heaps/src/art/Test913.java +++ b/test/913-heaps/src/art/Test913.java @@ -185,19 +185,40 @@ public class Test913 { } private static void doTestPrimitiveFieldsClasses() { + boolean correctHeapValue = false; + setTag(IntObject.class, 10000); - System.out.println(followReferencesPrimitiveFields(IntObject.class)); + String heapTrace = followReferencesPrimitiveFields(IntObject.class); + + if (!checkInitialized(IntObject.class)) { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000000"); + } else { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=3) 0000000000000005"); + } + + if (!correctHeapValue) + System.out.println("Heap Trace for IntObject is not as expected:\n" + heapTrace); + System.out.println(getTag(IntObject.class)); setTag(IntObject.class, 0); setTag(FloatObject.class, 10000); - System.out.println(followReferencesPrimitiveFields(FloatObject.class)); + heapTrace = followReferencesPrimitiveFields(FloatObject.class); + + if (!checkInitialized(FloatObject.class)) { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000000"); + } else { + correctHeapValue = heapTrace.equals("10000@0 (static, int, index=11) 0000000000000006"); + } + + if (!correctHeapValue) + System.out.println("Heap Trace for FloatObject is not as expected:\n" + heapTrace); + System.out.println(getTag(FloatObject.class)); setTag(FloatObject.class, 0); - boolean correctHeapValue = false; setTag(Inf1.class, 10000); - String heapTrace = followReferencesPrimitiveFields(Inf1.class); + heapTrace = followReferencesPrimitiveFields(Inf1.class); if (!checkInitialized(Inf1.class)) { correctHeapValue = heapTrace.equals("10000@0 (static, int, index=0) 0000000000000000"); diff --git a/test/knownfailures.json b/test/knownfailures.json index 5a67fbcc45..04de7a1a74 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -708,8 +708,8 @@ }, { "tests": "660-clinit", - "variant": "no-image | no-dex2oat | no-prebuild", - "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild and", - "--no-dex2oat do not create"] + "variant": "no-image | no-dex2oat | no-prebuild | interp-ac", + "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild, interp-ac", + "and --no-dex2oat do not create"] } ] @@ -119,6 +119,71 @@ function verbose_run() { env "$@" } +# Parse a colon-separated list into an array (e.g. "foo.dex:bar.dex" -> (foo.dex bar.dex)) +PARSE_CLASSPATH_RESULT=() # Return value will be here due to shell limitations. +parse_classpath() { + local cp="$1" + local oldifs=$IFS + + local cp_array + cp_array=() + + IFS=":" + for part in $cp; do + cp_array+=("$part") + done + IFS=$oldifs + + PARSE_CLASSPATH_RESULT=("${cp_array[@]}") +} + +# Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files. +# e.g. (-cp foo/classes.dex:bar/classes.dex) -> (foo/classes.dex bar/classes.dex) +find_cp_in_args() { + local found="false" + local index=0 + local what + + while [[ $# -gt 0 ]]; do + case "$1" in + -cp|-classpath) + parse_classpath "$2" + # Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files. + # Subsequent parses will overwrite the preceding. + shift + ;; + esac + shift + done +} + +# Delete the 'oat' directories relative to the classpath's dex files. +# e.g. (foo/classes.dex bar/classes.dex) would delete (foo/oat bar/oat) directories. +cleanup_oat_directory() { + local classpath + classpath=("$@") + + local dirpath + + for path in "${classpath[@]}"; do + dirpath="$(dirname "$path")" + [[ -d "$dirpath" ]] && verbose_run rm -rf "$dirpath/oat" + done +} + +# Parse -cp <CP>, -classpath <CP>, and $CLASSPATH to find the dex files. +# Each dex file's directory will have an 'oat' file directory, delete it. +# Input: Command line arguments to the art script. +# e.g. -cp foo/classes.dex:bar/classes.dex would delete (foo/oat bar/oat) directories. +cleanup_oat_directory_for_classpath() { + # First try: Use $CLASSPATH environment variable. + parse_classpath "$CLASSPATH" + # Second try: Look for latest -cp or -classpath arg which will take precedence. + find_cp_in_args "$@" + + cleanup_oat_directory "${PARSE_CLASSPATH_RESULT[@]}" +} + # Attempt to find $ANDROID_ROOT/framework/<isa>/core.art' without knowing what <isa> is. function check_if_boot_image_file_exists() { local image_location_dir="$1" @@ -154,6 +219,9 @@ function detect_boot_image_location() { function run_art() { local image_location="$(detect_boot_image_location)" + # First cleanup any left-over 'oat' files from the last time dalvikvm was run. + cleanup_oat_directory_for_classpath "$@" + # Run dalvikvm. verbose_run ANDROID_DATA=$ANDROID_DATA \ ANDROID_ROOT=$ANDROID_ROOT \ LD_LIBRARY_PATH=$LD_LIBRARY_PATH \ @@ -164,6 +232,9 @@ function run_art() { -Xnorelocate \ -Ximage:"$image_location" \ "$@" + + # Avoid polluting disk with 'oat' files after dalvikvm has finished. + cleanup_oat_directory_for_classpath "$@" } while [[ "$1" = "-"* ]]; do |