diff options
95 files changed, 3206 insertions, 763 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 1f210e1a62..c8f661561f 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -73,6 +73,11 @@ $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval $(call build-art-test-dex,art-gte ART_TEST_HOST_GTEST_MainStripped_DEX := $(basename $(ART_TEST_HOST_GTEST_Main_DEX))Stripped$(suffix $(ART_TEST_HOST_GTEST_Main_DEX)) ART_TEST_TARGET_GTEST_MainStripped_DEX := $(basename $(ART_TEST_TARGET_GTEST_Main_DEX))Stripped$(suffix $(ART_TEST_TARGET_GTEST_Main_DEX)) +# Create rules for MainUncompressed, a copy of Main with the classes.dex uncompressed +# for the dex2oat tests. +ART_TEST_HOST_GTEST_MainUncompressed_DEX := $(basename $(ART_TEST_HOST_GTEST_Main_DEX))Uncompressed$(suffix $(ART_TEST_HOST_GTEST_Main_DEX)) +ART_TEST_TARGET_GTEST_MainUncompressed_DEX := $(basename $(ART_TEST_TARGET_GTEST_Main_DEX))Uncompressed$(suffix $(ART_TEST_TARGET_GTEST_Main_DEX)) + $(ART_TEST_HOST_GTEST_MainStripped_DEX): $(ART_TEST_HOST_GTEST_Main_DEX) cp $< $@ $(call dexpreopt-remove-classes.dex,$@) @@ -81,6 +86,16 @@ $(ART_TEST_TARGET_GTEST_MainStripped_DEX): $(ART_TEST_TARGET_GTEST_Main_DEX) cp $< $@ $(call dexpreopt-remove-classes.dex,$@) +$(ART_TEST_HOST_GTEST_MainUncompressed_DEX): $(ART_TEST_HOST_GTEST_Main_DEX) $(ZIPALIGN) + cp $< $@ + $(call uncompress-dexs, $@) + $(call align-package, $@) + +$(ART_TEST_TARGET_GTEST_MainUncompressed_DEX): $(ART_TEST_TARGET_GTEST_Main_DEX) $(ZIPALIGN) + cp $< $@ + $(call uncompress-dexs, $@) + $(call align-package, $@) + ART_TEST_GTEST_VerifierDeps_SRC := $(abspath $(wildcard $(LOCAL_PATH)/VerifierDeps/*.smali)) ART_TEST_GTEST_VerifierDepsMulti_SRC := $(abspath $(wildcard $(LOCAL_PATH)/VerifierDepsMulti/*.smali)) ART_TEST_HOST_GTEST_VerifierDeps_DEX := $(dir $(ART_TEST_HOST_GTEST_Main_DEX))$(subst Main,VerifierDeps,$(basename $(notdir $(ART_TEST_HOST_GTEST_Main_DEX))))$(suffix $(ART_TEST_HOST_GTEST_Main_DEX)) @@ -111,7 +126,7 @@ ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods Prof ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested MultiDex ART_GTEST_dexlayout_test_DEX_DEPS := ManyMethods -ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps +ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps MainUncompressed ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle ART_GTEST_hiddenapi_test_DEX_DEPS := HiddenApi @@ -468,7 +483,7 @@ $$(gtest_rule): $$(gtest_exe) $$(gtest_deps) $(hide) ($$(call ART_TEST_SKIP,$$@) && set -o pipefail && \ ASAN_OPTIONS=detect_leaks=1 $$< 2>&1 | tee $$<.tmp.out >&2 && \ { $$(call ART_TEST_PASSED,$$@) ; rm $$<.tmp.out ; }) || \ - ( grep -q AddressSanitizer $$<.tmp.out && \ + ( grep -q AddressSanitizer $$<.tmp.out && export ANDROID_BUILD_TOP=`pwd` && \ { echo "ABI: 'x86_64'" | cat - $$<.tmp.out | development/scripts/stack | tail -n 3000 ; } ; \ rm $$<.tmp.out ; $$(call ART_TEST_FAILED,$$@)) endif @@ -733,6 +748,8 @@ $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_TARGET_GTEST_$(dir)_DEX $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_HOST_GTEST_$(dir)_DEX :=)) ART_TEST_HOST_GTEST_MainStripped_DEX := ART_TEST_TARGET_GTEST_MainStripped_DEX := +ART_TEST_HOST_GTEST_MainUncompressed_DEX := +ART_TEST_TARGET_GTEST_MainUncompressed_DEX := ART_TEST_GTEST_VerifierDeps_SRC := ART_TEST_HOST_GTEST_VerifierDeps_DEX := ART_TEST_TARGET_GTEST_VerifierDeps_DEX := diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index 70cc07eff0..5d672061df 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -375,7 +375,7 @@ TEST_F(CmdlineParserTest, TestJdwpProviderEmpty) { TEST_F(CmdlineParserTest, TestJdwpProviderDefault) { const char* opt_args = "-XjdwpProvider:default"; - EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kAdbConnection, opt_args, M::JdwpProvider); + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider); } // TEST_F TEST_F(CmdlineParserTest, TestJdwpProviderInternal) { diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 2bc7409bb6..d0d6bfd3ce 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -77,10 +77,10 @@ struct CmdlineType<JdwpProvider> : CmdlineTypeParser<JdwpProvider> { "Example: -XjdwpProvider:internal for internal jdwp implementation\n" "Example: -XjdwpProvider:adbconnection for adb connection mediated jdwp implementation\n" "Example: -XjdwpProvider:default for the default jdwp implementation" - " (currently adbconnection)\n"); - } else if (option == "internal") { + " (currently internal)\n"); + } else if (option == "internal" || option == "default") { return Result::Success(JdwpProvider::kInternal); - } else if (option == "adbconnection" || option == "default") { + } else if (option == "adbconnection") { return Result::Success(JdwpProvider::kAdbConnection); } else if (option == "none") { return Result::Success(JdwpProvider::kNone); diff --git a/compiler/dex/dex_to_dex_compiler.h b/compiler/dex/dex_to_dex_compiler.h index abd048167c..2105a9ded4 100644 --- a/compiler/dex/dex_to_dex_compiler.h +++ b/compiler/dex/dex_to_dex_compiler.h @@ -23,8 +23,8 @@ #include "base/bit_vector.h" #include "dex/dex_file.h" +#include "dex/invoke_type.h" #include "handle.h" -#include "invoke_type.h" #include "method_reference.h" #include "quicken_info.h" diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 70cbb01569..5c649855b6 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -384,6 +384,12 @@ static optimizer::DexToDexCompiler::CompilationLevel GetDexToDexCompilationLevel Thread* self, const CompilerDriver& driver, Handle<mirror::ClassLoader> class_loader, const DexFile& dex_file, const DexFile::ClassDef& class_def) REQUIRES_SHARED(Locks::mutator_lock_) { + // When the dex file is uncompressed in the APK, we do not generate a copy in the .vdex + // file. As a result, dex2oat will map the dex file read-only, and we only need to check + // that to know if we can do quickening. + if (dex_file.GetContainer() != nullptr && dex_file.GetContainer()->IsReadOnly()) { + return optimizer::DexToDexCompiler::CompilationLevel::kDontDexToDexCompile; + } auto* const runtime = Runtime::Current(); DCHECK(driver.GetCompilerOptions().IsQuickeningCompilationEnabled()); const char* descriptor = dex_file.GetClassDescriptor(class_def); @@ -872,6 +878,14 @@ void CompilerDriver::PreCompile(jobject class_loader, TimingLogger* timings) { CheckThreadPools(); + if (kUseBitstringTypeCheck && + !compiler_options_->IsBootImage() && + compiler_options_->IsAotCompilationEnabled()) { + RecordBootImageClassesWithAssignedBitstring(); + VLOG(compiler) << "RecordBootImageClassesWithAssignedBitstring: " + << GetMemoryUsageString(false); + } + LoadImageClasses(timings); VLOG(compiler) << "LoadImageClasses: " << GetMemoryUsageString(false); @@ -940,6 +954,43 @@ void CompilerDriver::PreCompile(jobject class_loader, } } +void CompilerDriver::RecordBootImageClassesWithAssignedBitstring() { + if (boot_image_classes_with_assigned_bitstring_ != nullptr) { + return; // Already recorded. (Happens because of class unloading between dex files.) + } + + class Visitor : public ClassVisitor { + public: + explicit Visitor(std::unordered_set<mirror::Class*>* recorded_classes) + : recorded_classes_(recorded_classes) {} + + bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE + REQUIRES(Locks::subtype_check_lock_) REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(klass != nullptr); + SubtypeCheckInfo::State state = SubtypeCheck<ObjPtr<mirror::Class>>::GetState(klass); + if (state == SubtypeCheckInfo::kAssigned) { + recorded_classes_->insert(klass.Ptr()); + } + return true; + } + + private: + std::unordered_set<mirror::Class*>* const recorded_classes_; + }; + + boot_image_classes_with_assigned_bitstring_.reset(new std::unordered_set<mirror::Class*>()); + Visitor visitor(boot_image_classes_with_assigned_bitstring_.get()); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + ScopedObjectAccess soa(Thread::Current()); + MutexLock subtype_check_lock(soa.Self(), *Locks::subtype_check_lock_); + class_linker->VisitClasses(&visitor); +} + +bool CompilerDriver::IsBootImageClassWithAssignedBitstring(ObjPtr<mirror::Class> klass) { + DCHECK(boot_image_classes_with_assigned_bitstring_ != nullptr); + return boot_image_classes_with_assigned_bitstring_->count(klass.Ptr()) != 0u; +} + bool CompilerDriver::IsImageClass(const char* descriptor) const { if (image_classes_ != nullptr) { // If we have a set of image classes, use those. diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 4b5916d572..fc77656761 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -110,13 +110,13 @@ class CompilerDriver { ~CompilerDriver(); - // Set dex files that will be stored in the oat file after being compiled. + // Set dex files associated with the oat file being compiled. void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files); // Set dex files classpath. void SetClasspathDexFiles(const std::vector<const DexFile*>& dex_files); - // Get dex file that will be stored in the oat file after being compiled. + // Get dex files associated with the the oat file being compiled. ArrayRef<const DexFile* const> GetDexFilesForOatFile() const { return ArrayRef<const DexFile* const>(dex_files_for_oat_file_); } @@ -385,12 +385,17 @@ class CompilerDriver { return dex_to_dex_compiler_; } + bool IsBootImageClassWithAssignedBitstring(ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_); + private: void PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); + void RecordBootImageClassesWithAssignedBitstring() REQUIRES(!Locks::mutator_lock_); + void LoadImageClasses(TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); // Attempt to resolve all type, methods, fields, and strings @@ -513,6 +518,12 @@ class CompilerDriver { // This option may be restricted to the boot image, depending on a flag in the implementation. std::unique_ptr<std::unordered_set<std::string>> methods_to_compile_; + // For AOT app compilation, we keep the set of boot image classes with assigned type check + // bitstring. We need to retrieve this set before we initialize app image classes as the + // initialization can cause more boot image bitstrings to be assigned. + // Note that boot image classes are non-moveable, so it's OK to keep raw pointers. + std::unique_ptr<std::unordered_set<mirror::Class*>> boot_image_classes_with_assigned_bitstring_; + std::atomic<uint32_t> number_of_soft_verifier_failures_; bool had_hard_verifier_failure_; @@ -533,7 +544,7 @@ class CompilerDriver { bool support_boot_image_fixup_; - // List of dex files that will be stored in the oat file. + // List of dex files associates with the oat file. std::vector<const DexFile*> dex_files_for_oat_file_; CompiledMethodStorage compiled_method_storage_; diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index 84ba178f7b..504c6479cc 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -2490,8 +2490,12 @@ void CodeGeneratorARMVIXL::GenerateFrameEntry() { } if (!skip_overflow_check) { - UseScratchRegisterScope temps(GetVIXLAssembler()); - vixl32::Register temp = temps.Acquire(); + // Using r4 instead of IP saves 2 bytes. Start by asserting that r4 is available here. + for (vixl32::Register reg : kParameterCoreRegistersVIXL) { + DCHECK(!reg.Is(r4)); + } + DCHECK(!kCoreCalleeSaves.Includes(r4)); + vixl32::Register temp = r4; __ Sub(temp, sp, Operand::From(GetStackOverflowReservedBytes(InstructionSet::kArm))); // The load must immediately precede RecordPcInfo. ExactAssemblyScope aas(GetVIXLAssembler(), diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index e81d97b0a8..02465d37ba 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -18,7 +18,7 @@ #define ART_COMPILER_OPTIMIZING_INLINER_H_ #include "dex/dex_file_types.h" -#include "invoke_type.h" +#include "dex/invoke_type.h" #include "jit/profile_compilation_info.h" #include "optimization.h" diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index 6928b70df7..acb830e524 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -19,9 +19,9 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "class_linker.h" +#include "dex/invoke_type.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" -#include "invoke_type.h" #include "mirror/dex_cache-inl.h" #include "nodes.h" #include "scoped_thread_state_change-inl.h" diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index b0657d6f1c..a9782a6afd 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -32,11 +32,11 @@ #include "deoptimization_kind.h" #include "dex/dex_file.h" #include "dex/dex_file_types.h" +#include "dex/invoke_type.h" #include "entrypoints/quick/quick_entrypoints_enum.h" #include "handle.h" #include "handle_scope.h" #include "intrinsics_enum.h" -#include "invoke_type.h" #include "locations.h" #include "method_reference.h" #include "mirror/class.h" diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc index dffef17587..12319df2e8 100644 --- a/compiler/optimizing/sharpening.cc +++ b/compiler/optimizing/sharpening.cc @@ -253,9 +253,9 @@ static inline bool CanUseTypeCheckBitstring(ObjPtr<mirror::Class> klass, // If the target is a boot image class, try to assign a type check bitstring (fall through). // (If --force-determinism, this was already done; repeating is OK and yields the same result.) } else { - // TODO: Use the bitstring also for AOT app compilation if the target class has a bitstring - // already assigned in the boot image. - return false; + // For AOT app compilation we can use the bitstring iff the target class is + // a boot image class with a bitstring already assigned in the boot image. + return compiler_driver->IsBootImageClassWithAssignedBitstring(klass); } // Try to assign a type check bitstring. diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 34ba4b34e7..a836d75a72 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1548,7 +1548,7 @@ class Dex2Oat FINAL { for (size_t i = 0, size = oat_writers_.size(); i != size; ++i) { rodata_.push_back(elf_writers_[i]->StartRoData()); // Unzip or copy dex files straight to the oat file. - std::unique_ptr<MemMap> opened_dex_files_map; + std::vector<std::unique_ptr<MemMap>> opened_dex_files_map; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; // No need to verify the dex file for: // 1) Dexlayout since it does the verification. It also may not pass the verification since @@ -1568,14 +1568,16 @@ class Dex2Oat FINAL { return dex2oat::ReturnCode::kOther; } dex_files_per_oat_file_.push_back(MakeNonOwningPointerVector(opened_dex_files)); - if (opened_dex_files_map != nullptr) { - opened_dex_files_maps_.push_back(std::move(opened_dex_files_map)); + if (opened_dex_files_map.empty()) { + DCHECK(opened_dex_files.empty()); + } else { + for (std::unique_ptr<MemMap>& map : opened_dex_files_map) { + opened_dex_files_maps_.push_back(std::move(map)); + } for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) { dex_file_oat_index_map_.emplace(dex_file.get(), i); opened_dex_files_.push_back(std::move(dex_file)); } - } else { - DCHECK(opened_dex_files.empty()); } } } diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index 8799540fd3..6fcf6952e8 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -1136,7 +1136,7 @@ TEST_F(Dex2oatClassLoaderContextTest, ContextWithStrippedDexFilesBackedByOdex) { std::string expected_classpath_key; { // Open the oat file to get the expected classpath. - OatFileAssistant oat_file_assistant(stripped_classpath.c_str(), kRuntimeISA, false); + OatFileAssistant oat_file_assistant(stripped_classpath.c_str(), kRuntimeISA, false, false); std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile()); std::vector<std::unique_ptr<const DexFile>> oat_dex_files = OatFileAssistant::LoadDexFiles(*oat_file, stripped_classpath.c_str()); @@ -1528,4 +1528,19 @@ TEST_F(Dex2oatDedupeCode, DedupeTest) { EXPECT_LT(dedupe_size, no_dedupe_size); } +TEST_F(Dex2oatTest, UncompressedTest) { + std::unique_ptr<const DexFile> dex(OpenTestDexFile("MainUncompressed")); + std::string out_dir = GetScratchDir(); + const std::string base_oat_name = out_dir + "/base.oat"; + GenerateOdexForTest(dex->GetLocation(), + base_oat_name, + CompilerFilter::Filter::kQuicken, + { }, + true, // expect_success + false, // use_fd + [](const OatFile& o) { + CHECK(!o.ContainsDexCode()); + }); +} + } // namespace art diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 637578e622..62519fc48e 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -253,7 +253,7 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, } std::vector<OutputStream*> rodata; - std::vector<std::unique_ptr<MemMap>> opened_dex_files_map; + std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; // Now that we have finalized key_value_store_, start writing the oat file. for (size_t i = 0, size = oat_writers.size(); i != size; ++i) { @@ -266,7 +266,7 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, dex_file->GetLocation().c_str(), dex_file->GetLocationChecksum()); - std::unique_ptr<MemMap> cur_opened_dex_files_map; + std::vector<std::unique_ptr<MemMap>> cur_opened_dex_files_maps; std::vector<std::unique_ptr<const DexFile>> cur_opened_dex_files; bool dex_files_ok = oat_writers[i]->WriteAndOpenDexFiles( vdex_files[i].GetFile(), @@ -276,12 +276,14 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, &key_value_store, /* verify */ false, // Dex files may be dex-to-dex-ed, don't verify. /* update_input_vdex */ false, - &cur_opened_dex_files_map, + &cur_opened_dex_files_maps, &cur_opened_dex_files); ASSERT_TRUE(dex_files_ok); - if (cur_opened_dex_files_map != nullptr) { - opened_dex_files_map.push_back(std::move(cur_opened_dex_files_map)); + if (!cur_opened_dex_files_maps.empty()) { + for (std::unique_ptr<MemMap>& cur_map : cur_opened_dex_files_maps) { + opened_dex_files_maps.push_back(std::move(cur_map)); + } for (std::unique_ptr<const DexFile>& cur_dex_file : cur_opened_dex_files) { // dex_file_oat_index_map_.emplace(dex_file.get(), i); opened_dex_files.push_back(std::move(cur_dex_file)); diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index f790db2b6c..b3a92e59ac 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -39,6 +39,7 @@ #include "dex/dex_file_types.h" #include "dex/standard_dex_file.h" #include "dex/verification_results.h" +#include "dex_container.h" #include "dexlayout.h" #include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" @@ -367,6 +368,7 @@ OatWriter::OatWriter(bool compiling_boot_image, compiler_driver_(nullptr), image_writer_(nullptr), compiling_boot_image_(compiling_boot_image), + only_contains_uncompressed_zip_entries_(false), dex_files_(nullptr), vdex_size_(0u), vdex_dex_files_offset_(0u), @@ -640,7 +642,7 @@ bool OatWriter::WriteAndOpenDexFiles( SafeMap<std::string, std::string>* key_value_store, bool verify, bool update_input_vdex, - /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, + /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { CHECK(write_state_ == WriteState::kAddingDexFileSources); @@ -649,7 +651,7 @@ bool OatWriter::WriteAndOpenDexFiles( return false; } - std::unique_ptr<MemMap> dex_files_map; + std::vector<std::unique_ptr<MemMap>> dex_files_map; std::vector<std::unique_ptr<const DexFile>> dex_files; // Initialize VDEX and OAT headers. @@ -2746,6 +2748,12 @@ class OatWriter::WriteQuickeningInfoOffsetsMethodVisitor { }; bool OatWriter::WriteQuickeningInfo(OutputStream* vdex_out) { + if (only_contains_uncompressed_zip_entries_) { + // Nothing to write. Leave `vdex_size_` untouched and unaligned. + vdex_quickening_info_offset_ = vdex_size_; + size_quickening_info_alignment_ = 0; + return true; + } size_t initial_offset = vdex_size_; // Make sure the table is properly aligned. size_t start_offset = RoundUp(initial_offset, 4u); @@ -3335,14 +3343,28 @@ bool OatWriter::WriteDexFiles(OutputStream* out, File* file, bool update_input_v vdex_dex_files_offset_ = vdex_size_; - // Write dex files. + only_contains_uncompressed_zip_entries_ = true; for (OatDexFile& oat_dex_file : oat_dex_files_) { - if (!WriteDexFile(out, file, &oat_dex_file, update_input_vdex)) { - return false; + if (!oat_dex_file.source_.IsZipEntry()) { + only_contains_uncompressed_zip_entries_ = false; + break; + } + ZipEntry* entry = oat_dex_file.source_.GetZipEntry(); + if (!entry->IsUncompressed() || !entry->IsAlignedToDexHeader()) { + only_contains_uncompressed_zip_entries_ = false; + break; + } + } + + if (!only_contains_uncompressed_zip_entries_) { + // Write dex files. + for (OatDexFile& oat_dex_file : oat_dex_files_) { + if (!WriteDexFile(out, file, &oat_dex_file, update_input_vdex)) { + return false; + } } } - CloseSources(); return true; } @@ -3496,18 +3518,21 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil return false; } Options options; - options.output_to_memmap_ = true; options.compact_dex_level_ = compact_dex_level_; options.update_checksum_ = true; - DexLayout dex_layout(options, profile_compilation_info_, nullptr); - dex_layout.ProcessDexFile(location.c_str(), dex_file.get(), 0); - std::unique_ptr<MemMap> mem_map(dex_layout.GetAndReleaseMemMap()); + DexLayout dex_layout(options, profile_compilation_info_, /*file*/ nullptr, /*header*/ nullptr); + std::unique_ptr<DexContainer> out_data; + dex_layout.ProcessDexFile(location.c_str(), dex_file.get(), 0, &out_data); oat_dex_file->dex_sections_layout_ = dex_layout.GetSections(); // Dex layout can affect the size of the dex file, so we update here what we have set // when adding the dex file as a source. - const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(mem_map->Begin()); + const UnalignedDexFileHeader* header = + AsUnalignedDexFileHeader(out_data->GetMainSection()->Begin()); oat_dex_file->dex_file_size_ = header->file_size_; - if (!WriteDexFile(out, oat_dex_file, mem_map->Begin(), /* update_input_vdex */ false)) { + if (!WriteDexFile(out, + oat_dex_file, + out_data->GetMainSection()->Begin(), + /* update_input_vdex */ false)) { return false; } CHECK_EQ(oat_dex_file->dex_file_location_checksum_, dex_file->GetLocationChecksum()); @@ -3682,7 +3707,7 @@ bool OatWriter::WriteDexFile(OutputStream* out, bool OatWriter::OpenDexFiles( File* file, bool verify, - /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, + /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) { TimingLogger::ScopedTiming split("OpenDexFiles", timings_); @@ -3691,6 +3716,44 @@ bool OatWriter::OpenDexFiles( return true; } + if (only_contains_uncompressed_zip_entries_) { + std::vector<std::unique_ptr<const DexFile>> dex_files; + std::vector<std::unique_ptr<MemMap>> maps; + for (OatDexFile& oat_dex_file : oat_dex_files_) { + std::string error_msg; + MemMap* map = oat_dex_file.source_.GetZipEntry()->MapDirectlyFromFile( + oat_dex_file.dex_file_location_data_, &error_msg); + if (map == nullptr) { + LOG(ERROR) << error_msg; + return false; + } + maps.emplace_back(map); + // Now, open the dex file. + const ArtDexFileLoader dex_file_loader; + dex_files.emplace_back(dex_file_loader.Open(map->Begin(), + map->Size(), + oat_dex_file.GetLocation(), + oat_dex_file.dex_file_location_checksum_, + /* oat_dex_file */ nullptr, + verify, + verify, + &error_msg)); + if (dex_files.back() == nullptr) { + LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation() + << " Error: " << error_msg; + return false; + } + oat_dex_file.class_offsets_.resize(dex_files.back()->GetHeader().class_defs_size_); + } + *opened_dex_files_map = std::move(maps); + *opened_dex_files = std::move(dex_files); + CloseSources(); + return true; + } + // We could have closed the sources at the point of writing the dex files, but to + // make it consistent with the case we're not writing the dex files, we close them now. + CloseSources(); + size_t map_offset = oat_dex_files_[0].dex_file_offset_; size_t length = vdex_size_ - map_offset; @@ -3750,7 +3813,7 @@ bool OatWriter::OpenDexFiles( oat_dex_file.class_offsets_.resize(dex_files.back()->GetHeader().class_defs_size_); } - *opened_dex_files_map = std::move(dex_files_map); + opened_dex_files_map->push_back(std::move(dex_files_map)); *opened_dex_files = std::move(dex_files); return true; } diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h index ac96bb8d56..c08c05a93b 100644 --- a/dex2oat/linker/oat_writer.h +++ b/dex2oat/linker/oat_writer.h @@ -174,7 +174,7 @@ class OatWriter { SafeMap<std::string, std::string>* key_value_store, bool verify, bool update_input_vdex, - /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, + /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); bool WriteQuickeningInfo(OutputStream* vdex_out); bool WriteVerifierDeps(OutputStream* vdex_out, verifier::VerifierDeps* verifier_deps); @@ -303,7 +303,7 @@ class OatWriter { bool update_input_vdex); bool OpenDexFiles(File* file, bool verify, - /*out*/ std::unique_ptr<MemMap>* opened_dex_files_map, + /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map, /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files); size_t InitOatHeader(InstructionSet instruction_set, @@ -370,6 +370,8 @@ class OatWriter { const CompilerDriver* compiler_driver_; ImageWriter* image_writer_; const bool compiling_boot_image_; + // Whether the dex files being compiled are all uncompressed in the APK. + bool only_contains_uncompressed_zip_entries_; // note OatFile does not take ownership of the DexFiles const std::vector<const DexFile*>* dex_files_; diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index 4e5fd72675..cd6ca51dda 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -188,7 +188,7 @@ class OatTest : public CommonCompilerTest { oat_file); elf_writer->Start(); OutputStream* oat_rodata = elf_writer->StartRoData(); - std::unique_ptr<MemMap> opened_dex_files_map; + std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps; std::vector<std::unique_ptr<const DexFile>> opened_dex_files; if (!oat_writer.WriteAndOpenDexFiles(vdex_file, oat_rodata, @@ -197,7 +197,7 @@ class OatTest : public CommonCompilerTest { &key_value_store, verify, /* update_input_vdex */ false, - &opened_dex_files_map, + &opened_dex_files_maps, &opened_dex_files)) { return false; } @@ -257,7 +257,9 @@ class OatTest : public CommonCompilerTest { return false; } - opened_dex_files_maps_.emplace_back(std::move(opened_dex_files_map)); + for (std::unique_ptr<MemMap>& map : opened_dex_files_maps) { + opened_dex_files_maps_.emplace_back(std::move(map)); + } for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) { opened_dex_files_.emplace_back(dex_file.release()); } diff --git a/dexlayout/compact_dex_writer.cc b/dexlayout/compact_dex_writer.cc index dd1eee7c59..2f601b6b1a 100644 --- a/dexlayout/compact_dex_writer.cc +++ b/dexlayout/compact_dex_writer.cc @@ -24,20 +24,20 @@ namespace art { -CompactDexWriter::CompactDexWriter(dex_ir::Header* header, - MemMap* mem_map, - DexLayout* dex_layout, - CompactDexLevel compact_dex_level) - : DexWriter(header, mem_map, dex_layout, /*compute_offsets*/ true), - compact_dex_level_(compact_dex_level), - data_dedupe_(/*bucket_count*/ 32, - HashedMemoryRange::HashEqual(mem_map->Begin()), - HashedMemoryRange::HashEqual(mem_map->Begin())) { - CHECK(compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone); +CompactDexWriter::CompactDexWriter(DexLayout* dex_layout) + : DexWriter(dex_layout, /*compute_offsets*/ true) { + CHECK(GetCompactDexLevel() != CompactDexLevel::kCompactDexLevelNone); } -uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(uint32_t offset) { - const uint32_t start_offset = offset; +CompactDexLevel CompactDexWriter::GetCompactDexLevel() const { + return dex_layout_->GetOptions().compact_dex_level_; +} + +CompactDexWriter::Container::Container(bool dedupe_code_items) + : code_item_dedupe_(dedupe_code_items, &data_section_) {} + +uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(Stream* stream) { + const uint32_t start_offset = stream->Tell(); const dex_ir::Collections& collections = header_->GetCollections(); // Debug offsets for method indexes. 0 means no debug info. std::vector<uint32_t> debug_info_offsets(collections.MethodIdsSize(), 0u); @@ -79,15 +79,16 @@ uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(uint32_t offset) { &debug_info_base_, &debug_info_offsets_table_offset_); // Align the table and write it out. - offset = RoundUp(offset, CompactDexDebugInfoOffsetTable::kAlignment); - debug_info_offsets_pos_ = offset; - offset += Write(data.data(), data.size(), offset); + stream->AlignTo(CompactDexDebugInfoOffsetTable::kAlignment); + debug_info_offsets_pos_ = stream->Tell(); + stream->Write(data.data(), data.size()); // Verify that the whole table decodes as expected and measure average performance. const bool kMeasureAndTestOutput = dex_layout_->GetOptions().verify_output_; if (kMeasureAndTestOutput && !debug_info_offsets.empty()) { uint64_t start_time = NanoTime(); - CompactDexDebugInfoOffsetTable::Accessor accessor(mem_map_->Begin() + debug_info_offsets_pos_, + stream->Begin(); + CompactDexDebugInfoOffsetTable::Accessor accessor(stream->Begin() + debug_info_offsets_pos_, debug_info_base_, debug_info_offsets_table_offset_); @@ -99,19 +100,19 @@ uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(uint32_t offset) { << (end_time - start_time) / debug_info_offsets.size(); } - return offset - start_offset; + return stream->Tell() - start_offset; } -uint32_t CompactDexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, - uint32_t offset, +uint32_t CompactDexWriter::WriteCodeItem(Stream* stream, + dex_ir::CodeItem* code_item, bool reserve_only) { DCHECK(code_item != nullptr); DCHECK(!reserve_only) << "Not supported because of deduping."; - const uint32_t start_offset = offset; + const uint32_t start_offset = stream->Tell(); // Align to minimum requirements, additional alignment requirements are handled below after we // know the preheader size. - offset = RoundUp(offset, CompactDexFile::CodeItem::kAlignment); + stream->AlignTo(CompactDexFile::CodeItem::kAlignment); CompactDexFile::CodeItem disk_code_item; @@ -127,7 +128,7 @@ uint32_t CompactDexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, const size_t preheader_bytes = (preheader_end - preheader) * sizeof(preheader[0]); static constexpr size_t kPayloadInstructionRequiredAlignment = 4; - const uint32_t current_code_item_start = offset + preheader_bytes; + const uint32_t current_code_item_start = stream->Tell() + preheader_bytes; if (!IsAlignedParam(current_code_item_start, kPayloadInstructionRequiredAlignment)) { // If the preheader is going to make the code unaligned, consider adding 2 bytes of padding // before if required. @@ -137,49 +138,60 @@ uint32_t CompactDexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, if (opcode == Instruction::FILL_ARRAY_DATA || opcode == Instruction::PACKED_SWITCH || opcode == Instruction::SPARSE_SWITCH) { - offset += RoundUp(current_code_item_start, kPayloadInstructionRequiredAlignment) - - current_code_item_start; + stream->Skip( + RoundUp(current_code_item_start, kPayloadInstructionRequiredAlignment) - + current_code_item_start); break; } } } - const uint32_t data_start = offset; + const uint32_t data_start = stream->Tell(); // Write preheader first. - offset += Write(reinterpret_cast<const uint8_t*>(preheader), preheader_bytes, offset); + stream->Write(reinterpret_cast<const uint8_t*>(preheader), preheader_bytes); // Registered offset is after the preheader. - ProcessOffset(&offset, code_item); + ProcessOffset(stream, code_item); // Avoid using sizeof so that we don't write the fake instruction array at the end of the code // item. - offset += Write(&disk_code_item, - OFFSETOF_MEMBER(CompactDexFile::CodeItem, insns_), - offset); + stream->Write(&disk_code_item, OFFSETOF_MEMBER(CompactDexFile::CodeItem, insns_)); // Write the instructions. - offset += Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t), offset); + stream->Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t)); // Write the post instruction data. - offset += WriteCodeItemPostInstructionData(code_item, offset, reserve_only); + WriteCodeItemPostInstructionData(stream, code_item, reserve_only); - if (dex_layout_->GetOptions().dedupe_code_items_ && compute_offsets_) { - // After having written, try to dedupe the whole code item (excluding padding). - uint32_t deduped_offset = DedupeData(data_start, offset, code_item->GetOffset()); - if (deduped_offset != kDidNotDedupe) { + if (compute_offsets_) { + // After having written, maybe dedupe the whole code item (excluding padding). + const uint32_t deduped_offset = code_item_dedupe_->Dedupe(data_start, + stream->Tell(), + code_item->GetOffset()); + if (deduped_offset != Deduper::kDidNotDedupe) { code_item->SetOffset(deduped_offset); // Undo the offset for all that we wrote since we deduped. - offset = start_offset; + stream->Seek(start_offset); } } - return offset - start_offset; + return stream->Tell() - start_offset; } -uint32_t CompactDexWriter::DedupeData(uint32_t data_start, - uint32_t data_end, - uint32_t item_offset) { + +CompactDexWriter::Deduper::Deduper(bool enabled, DexContainer::Section* section) + : enabled_(enabled), + dedupe_map_(/*bucket_count*/ 32, + HashedMemoryRange::HashEqual(section), + HashedMemoryRange::HashEqual(section)) {} + +uint32_t CompactDexWriter::Deduper::Dedupe(uint32_t data_start, + uint32_t data_end, + uint32_t item_offset) { + if (!enabled_) { + return kDidNotDedupe; + } HashedMemoryRange range {data_start, data_end - data_start}; - auto existing = data_dedupe_.emplace(range, item_offset); + auto existing = dedupe_map_.emplace(range, item_offset); if (!existing.second) { - // Failed to insert, item already existed in the map. + // Failed to insert means we deduped, return the existing item offset. return existing.first->second; } return kDidNotDedupe; @@ -223,7 +235,7 @@ void CompactDexWriter::SortDebugInfosByMethodIndex() { }); } -void CompactDexWriter::WriteHeader() { +void CompactDexWriter::WriteHeader(Stream* stream) { CompactDexFile::Header header; CompactDexFile::WriteMagic(&header.magic_[0]); CompactDexFile::WriteCurrentVersion(&header.magic_[0]); @@ -263,78 +275,99 @@ void CompactDexWriter::WriteHeader() { if (header_->SupportDefaultMethods()) { header.feature_flags_ |= static_cast<uint32_t>(CompactDexFile::FeatureFlags::kDefaultMethods); } - UNUSED(Write(reinterpret_cast<uint8_t*>(&header), sizeof(header), 0u)); + stream->Seek(0); + stream->Overwrite(reinterpret_cast<uint8_t*>(&header), sizeof(header)); } size_t CompactDexWriter::GetHeaderSize() const { return sizeof(CompactDexFile::Header); } -void CompactDexWriter::WriteMemMap() { +void CompactDexWriter::Write(DexContainer* output) { + CHECK(output->IsCompactDexContainer()); + Container* const container = down_cast<Container*>(output); + // For now, use the same stream for both data and metadata. + Stream stream(output->GetMainSection()); + Stream* main_stream = &stream; + Stream* data_stream = &stream; + code_item_dedupe_ = &container->code_item_dedupe_; + // Starting offset is right after the header. - uint32_t offset = GetHeaderSize(); + main_stream->Seek(GetHeaderSize()); dex_ir::Collections& collection = header_->GetCollections(); // Based on: https://source.android.com/devices/tech/dalvik/dex-format // Since the offsets may not be calculated already, the writing must be done in the correct order. - const uint32_t string_ids_offset = offset; - offset += WriteStringIds(offset, /*reserve_only*/ true); - offset += WriteTypeIds(offset); - const uint32_t proto_ids_offset = offset; - offset += WriteProtoIds(offset, /*reserve_only*/ true); - offset += WriteFieldIds(offset); - offset += WriteMethodIds(offset); - const uint32_t class_defs_offset = offset; - offset += WriteClassDefs(offset, /*reserve_only*/ true); - const uint32_t call_site_ids_offset = offset; - offset += WriteCallSiteIds(offset, /*reserve_only*/ true); - offset += WriteMethodHandles(offset); + const uint32_t string_ids_offset = main_stream->Tell(); + WriteStringIds(main_stream, /*reserve_only*/ true); + WriteTypeIds(main_stream); + const uint32_t proto_ids_offset = main_stream->Tell(); + WriteProtoIds(main_stream, /*reserve_only*/ true); + WriteFieldIds(main_stream); + WriteMethodIds(main_stream); + const uint32_t class_defs_offset = main_stream->Tell(); + WriteClassDefs(main_stream, /*reserve_only*/ true); + const uint32_t call_site_ids_offset = main_stream->Tell(); + WriteCallSiteIds(main_stream, /*reserve_only*/ true); + WriteMethodHandles(main_stream); uint32_t data_offset_ = 0u; if (compute_offsets_) { // Data section. - offset = RoundUp(offset, kDataSectionAlignment); - data_offset_ = offset; + data_stream->AlignTo(kDataSectionAlignment); + data_offset_ = data_stream->Tell(); } // Write code item first to minimize the space required for encoded methods. // For cdex, the code items don't depend on the debug info. - offset += WriteCodeItems(offset, /*reserve_only*/ false); + WriteCodeItems(data_stream, /*reserve_only*/ false); // Sort the debug infos by method index order, this reduces size by ~0.1% by reducing the size of // the debug info offset table. SortDebugInfosByMethodIndex(); - offset += WriteDebugInfoItems(offset); + WriteDebugInfoItems(data_stream); - offset += WriteEncodedArrays(offset); - offset += WriteAnnotations(offset); - offset += WriteAnnotationSets(offset); - offset += WriteAnnotationSetRefs(offset); - offset += WriteAnnotationsDirectories(offset); - offset += WriteTypeLists(offset); - offset += WriteClassDatas(offset); - offset += WriteStringDatas(offset); + WriteEncodedArrays(data_stream); + WriteAnnotations(data_stream); + WriteAnnotationSets(data_stream); + WriteAnnotationSetRefs(data_stream); + WriteAnnotationsDirectories(data_stream); + WriteTypeLists(data_stream); + WriteClassDatas(data_stream); + WriteStringDatas(data_stream); // Write delayed id sections that depend on data sections. - WriteStringIds(string_ids_offset, /*reserve_only*/ false); - WriteProtoIds(proto_ids_offset, /*reserve_only*/ false); - WriteClassDefs(class_defs_offset, /*reserve_only*/ false); - WriteCallSiteIds(call_site_ids_offset, /*reserve_only*/ false); + { + Stream::ScopedSeek seek(main_stream, string_ids_offset); + WriteStringIds(main_stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(main_stream, proto_ids_offset); + WriteProtoIds(main_stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(main_stream, class_defs_offset); + WriteClassDefs(main_stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(main_stream, call_site_ids_offset); + WriteCallSiteIds(main_stream, /*reserve_only*/ false); + } // Write the map list. if (compute_offsets_) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeMapList)); - collection.SetMapListOffset(offset); + data_stream->AlignTo(SectionAlignment(DexFile::kDexTypeMapList)); + collection.SetMapListOffset(data_stream->Tell()); } else { - offset = collection.MapListOffset(); + data_stream->Seek(collection.MapListOffset()); } - offset += GenerateAndWriteMapItems(offset); - offset = RoundUp(offset, kDataSectionAlignment); + GenerateAndWriteMapItems(data_stream); + data_stream->AlignTo(kDataSectionAlignment); // Map items are included in the data section. if (compute_offsets_) { - header_->SetDataSize(offset - data_offset_); + header_->SetDataSize(data_stream->Tell() - data_offset_); if (header_->DataSize() != 0) { // Offset must be zero when the size is zero. header_->SetDataOffset(data_offset_); @@ -348,25 +381,34 @@ void CompactDexWriter::WriteMemMap() { if (link_data.size() > 0) { CHECK_EQ(header_->LinkSize(), static_cast<uint32_t>(link_data.size())); if (compute_offsets_) { - header_->SetLinkOffset(offset); + header_->SetLinkOffset(data_stream->Tell()); + } else { + data_stream->Seek(header_->LinkOffset()); } - offset += Write(&link_data[0], link_data.size(), header_->LinkOffset()); + data_stream->Write(&link_data[0], link_data.size()); } // Write debug info offset table last to make dex file verifier happy. - offset += WriteDebugInfoOffsetTable(offset); + WriteDebugInfoOffsetTable(data_stream); // Write header last. if (compute_offsets_) { - header_->SetFileSize(offset); + header_->SetFileSize(main_stream->Tell()); } - WriteHeader(); + WriteHeader(main_stream); if (dex_layout_->GetOptions().update_checksum_) { - header_->SetChecksum(DexFile::CalculateChecksum(mem_map_->Begin(), offset)); + header_->SetChecksum(DexFile::CalculateChecksum(main_stream->Begin(), header_->FileSize())); // Rewrite the header with the calculated checksum. - WriteHeader(); + WriteHeader(main_stream); } + // Trim the map to make it sized as large as the dex file. + output->GetMainSection()->Resize(header_->FileSize()); +} + +std::unique_ptr<DexContainer> CompactDexWriter::CreateDexContainer() const { + return std::unique_ptr<DexContainer>( + new CompactDexWriter::Container(dex_layout_->GetOptions().dedupe_code_items_)); } } // namespace art diff --git a/dexlayout/compact_dex_writer.h b/dexlayout/compact_dex_writer.h index cb53caebc6..626b85a86b 100644 --- a/dexlayout/compact_dex_writer.h +++ b/dexlayout/compact_dex_writer.h @@ -19,6 +19,7 @@ #ifndef ART_DEXLAYOUT_COMPACT_DEX_WRITER_H_ #define ART_DEXLAYOUT_COMPACT_DEX_WRITER_H_ +#include <memory> // For unique_ptr #include <unordered_map> #include "dex_writer.h" @@ -26,62 +27,108 @@ namespace art { -class HashedMemoryRange { +// Compact dex writer for a single dex. +class CompactDexWriter : public DexWriter { public: - uint32_t offset_; - uint32_t length_; + explicit CompactDexWriter(DexLayout* dex_layout); - class HashEqual { + protected: + class Deduper { public: - explicit HashEqual(const uint8_t* data) : data_(data) {} + static const uint32_t kDidNotDedupe = 0; - // Equal function. - bool operator()(const HashedMemoryRange& a, const HashedMemoryRange& b) const { - return a.length_ == b.length_ && std::equal(data_ + a.offset_, - data_ + a.offset_ + a.length_, - data_ + b.offset_); - } + // if not enabled, Dedupe will always return kDidNotDedupe. + explicit Deduper(bool enabled, DexContainer::Section* section); - // Hash function. - size_t operator()(const HashedMemoryRange& range) const { - return HashBytes(data_ + range.offset_, range.length_); - } + // Deduplicate a blob of data that has been written to mem_map. + // Returns the offset of the deduplicated data or kDidNotDedupe did deduplication did not occur. + uint32_t Dedupe(uint32_t data_start, uint32_t data_end, uint32_t item_offset); private: - const uint8_t* data_; + class HashedMemoryRange { + public: + uint32_t offset_; + uint32_t length_; + + class HashEqual { + public: + explicit HashEqual(DexContainer::Section* section) : section_(section) {} + + // Equal function. + bool operator()(const HashedMemoryRange& a, const HashedMemoryRange& b) const { + if (a.length_ != b.length_) { + return false; + } + const uint8_t* data = Data(); + return std::equal(data + a.offset_, data + a.offset_ + a.length_, data + b.offset_); + } + + // Hash function. + size_t operator()(const HashedMemoryRange& range) const { + return HashBytes(Data() + range.offset_, range.length_); + } + + ALWAYS_INLINE uint8_t* Data() const { + return section_->Begin(); + } + + private: + DexContainer::Section* const section_; + }; + }; + + const bool enabled_; + + // Dedupe map. + std::unordered_map<HashedMemoryRange, + uint32_t, + HashedMemoryRange::HashEqual, + HashedMemoryRange::HashEqual> dedupe_map_; }; -}; -class CompactDexWriter : public DexWriter { public: - CompactDexWriter(dex_ir::Header* header, - MemMap* mem_map, - DexLayout* dex_layout, - CompactDexLevel compact_dex_level); + class Container : public DexContainer { + public: + Section* GetMainSection() OVERRIDE { + return &main_section_; + } + + Section* GetDataSection() OVERRIDE { + return &data_section_; + } + + bool IsCompactDexContainer() const OVERRIDE { + return true; + } + + private: + explicit Container(bool dedupe_code_items); + + VectorSection main_section_; + VectorSection data_section_; + Deduper code_item_dedupe_; + + friend class CompactDexWriter; + }; protected: - void WriteMemMap() OVERRIDE; + void Write(DexContainer* output) OVERRIDE; + + std::unique_ptr<DexContainer> CreateDexContainer() const OVERRIDE; - void WriteHeader() OVERRIDE; + void WriteHeader(Stream* stream) OVERRIDE; size_t GetHeaderSize() const OVERRIDE; - uint32_t WriteDebugInfoOffsetTable(uint32_t offset); + uint32_t WriteDebugInfoOffsetTable(Stream* stream); - uint32_t WriteCodeItem(dex_ir::CodeItem* code_item, uint32_t offset, bool reserve_only) OVERRIDE; + uint32_t WriteCodeItem(Stream* stream, dex_ir::CodeItem* code_item, bool reserve_only) OVERRIDE; void SortDebugInfosByMethodIndex(); - // Deduplicate a blob of data that has been written to mem_map. The backing storage is the actual - // mem_map contents to reduce RAM usage. - // Returns the offset of the deduplicated data or 0 if kDidNotDedupe did not occur. - uint32_t DedupeData(uint32_t data_start, uint32_t data_end, uint32_t item_offset); + CompactDexLevel GetCompactDexLevel() const; private: - const CompactDexLevel compact_dex_level_; - - static const uint32_t kDidNotDedupe = 0; - // Position in the compact dex file for the debug info table data starts. uint32_t debug_info_offsets_pos_ = 0u; @@ -91,11 +138,8 @@ class CompactDexWriter : public DexWriter { // Base offset of where debug info starts in the dex file. uint32_t debug_info_base_ = 0u; - // Dedupe map. - std::unordered_map<HashedMemoryRange, - uint32_t, - HashedMemoryRange::HashEqual, - HashedMemoryRange::HashEqual> data_dedupe_; + // State for where we are deduping. + Deduper* code_item_dedupe_ = nullptr; DISALLOW_COPY_AND_ASSIGN(CompactDexWriter); }; diff --git a/dexlayout/dex_container.h b/dexlayout/dex_container.h new file mode 100644 index 0000000000..7c426cbc53 --- /dev/null +++ b/dexlayout/dex_container.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 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. + * + * Header file of an in-memory representation of DEX files. + */ + +#ifndef ART_DEXLAYOUT_DEX_CONTAINER_H_ +#define ART_DEXLAYOUT_DEX_CONTAINER_H_ + +#include <vector> + +namespace art { + +// Dex container holds the artifacts produced by dexlayout and contains up to two sections: a main +// section and a data section. +// This container may also hold metadata used for multi dex deduplication in the future. +class DexContainer { + public: + virtual ~DexContainer() {} + + class Section { + public: + virtual ~Section() {} + + // Returns the start of the memory region. + virtual uint8_t* Begin() = 0; + + // Size in bytes. + virtual size_t Size() const = 0; + + // Resize the backing storage. + virtual void Resize(size_t size) = 0; + + // Returns the end of the memory region. + uint8_t* End() { + return Begin() + Size(); + } + }; + + // Vector backed section. + class VectorSection : public Section { + public: + virtual ~VectorSection() {} + + uint8_t* Begin() OVERRIDE { + return &data_[0]; + } + + size_t Size() const OVERRIDE { + return data_.size(); + } + + void Resize(size_t size) OVERRIDE { + data_.resize(size, 0u); + } + + private: + std::vector<uint8_t> data_; + }; + + virtual Section* GetMainSection() = 0; + virtual Section* GetDataSection() = 0; + virtual bool IsCompactDexContainer() const = 0; +}; + +} // namespace art + +#endif // ART_DEXLAYOUT_DEX_CONTAINER_H_ diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h index 3627717abe..1a84d2307d 100644 --- a/dexlayout/dex_ir.h +++ b/dexlayout/dex_ir.h @@ -486,11 +486,11 @@ class Item { virtual ~Item() { } // Return the assigned offset. - uint32_t GetOffset() const { + uint32_t GetOffset() const WARN_UNUSED { CHECK(OffsetAssigned()); return offset_; } - uint32_t GetSize() const { return size_; } + uint32_t GetSize() const WARN_UNUSED { return size_; } void SetOffset(uint32_t offset) { offset_ = offset; } void SetSize(uint32_t size) { size_ = size; } bool OffsetAssigned() const { diff --git a/dexlayout/dex_writer.cc b/dexlayout/dex_writer.cc index d26c9481b4..eb038a008b 100644 --- a/dexlayout/dex_writer.cc +++ b/dexlayout/dex_writer.cc @@ -30,7 +30,7 @@ namespace art { -size_t EncodeIntValue(int32_t value, uint8_t* buffer) { +static size_t EncodeIntValue(int32_t value, uint8_t* buffer) { size_t length = 0; if (value >= 0) { while (value > 0x7f) { @@ -47,7 +47,7 @@ size_t EncodeIntValue(int32_t value, uint8_t* buffer) { return length; } -size_t EncodeUIntValue(uint32_t value, uint8_t* buffer) { +static size_t EncodeUIntValue(uint32_t value, uint8_t* buffer) { size_t length = 0; do { buffer[length++] = static_cast<uint8_t>(value); @@ -56,7 +56,7 @@ size_t EncodeUIntValue(uint32_t value, uint8_t* buffer) { return length; } -size_t EncodeLongValue(int64_t value, uint8_t* buffer) { +static size_t EncodeLongValue(int64_t value, uint8_t* buffer) { size_t length = 0; if (value >= 0) { while (value > 0x7f) { @@ -78,7 +78,7 @@ union FloatUnion { uint32_t i_; }; -size_t EncodeFloatValue(float value, uint8_t* buffer) { +static size_t EncodeFloatValue(float value, uint8_t* buffer) { FloatUnion float_union; float_union.f_ = value; uint32_t int_value = float_union.i_; @@ -95,7 +95,7 @@ union DoubleUnion { uint64_t l_; }; -size_t EncodeDoubleValue(double value, uint8_t* buffer) { +static size_t EncodeDoubleValue(double value, uint8_t* buffer) { DoubleUnion double_union; double_union.d_ = value; uint64_t long_value = double_union.l_; @@ -107,26 +107,13 @@ size_t EncodeDoubleValue(double value, uint8_t* buffer) { return 7 - index; } -size_t DexWriter::Write(const void* buffer, size_t length, size_t offset) { - DCHECK_LE(offset + length, mem_map_->Size()); - memcpy(mem_map_->Begin() + offset, buffer, length); - return length; -} +DexWriter::DexWriter(DexLayout* dex_layout, bool compute_offsets) + : header_(dex_layout->GetHeader()), + dex_layout_(dex_layout), + compute_offsets_(compute_offsets) {} -size_t DexWriter::WriteSleb128(uint32_t value, size_t offset) { - uint8_t buffer[8]; - EncodeSignedLeb128(buffer, value); - return Write(buffer, SignedLeb128Size(value), offset); -} - -size_t DexWriter::WriteUleb128(uint32_t value, size_t offset) { - uint8_t buffer[8]; - EncodeUnsignedLeb128(buffer, value); - return Write(buffer, UnsignedLeb128Size(value), offset); -} - -size_t DexWriter::WriteEncodedValue(dex_ir::EncodedValue* encoded_value, size_t offset) { - size_t original_offset = offset; +size_t DexWriter::WriteEncodedValue(Stream* stream, dex_ir::EncodedValue* encoded_value) { + size_t original_offset = stream->Tell(); size_t start = 0; size_t length; uint8_t buffer[8]; @@ -175,284 +162,285 @@ size_t DexWriter::WriteEncodedValue(dex_ir::EncodedValue* encoded_value, size_t length = EncodeUIntValue(encoded_value->GetMethodId()->GetIndex(), buffer); break; case DexFile::kDexAnnotationArray: - offset += WriteEncodedValueHeader(type, 0, offset); - offset += WriteEncodedArray(encoded_value->GetEncodedArray()->GetEncodedValues(), offset); - return offset - original_offset; + WriteEncodedValueHeader(stream, type, 0); + WriteEncodedArray(stream, encoded_value->GetEncodedArray()->GetEncodedValues()); + return stream->Tell() - original_offset; case DexFile::kDexAnnotationAnnotation: - offset += WriteEncodedValueHeader(type, 0, offset); - offset += WriteEncodedAnnotation(encoded_value->GetEncodedAnnotation(), offset); - return offset - original_offset; + WriteEncodedValueHeader(stream, type, 0); + WriteEncodedAnnotation(stream, encoded_value->GetEncodedAnnotation()); + return stream->Tell() - original_offset; case DexFile::kDexAnnotationNull: - return WriteEncodedValueHeader(type, 0, offset); + return WriteEncodedValueHeader(stream, type, 0); case DexFile::kDexAnnotationBoolean: - return WriteEncodedValueHeader(type, encoded_value->GetBoolean() ? 1 : 0, offset); + return WriteEncodedValueHeader(stream, type, encoded_value->GetBoolean() ? 1 : 0); default: return 0; } - offset += WriteEncodedValueHeader(type, length - 1, offset); - offset += Write(buffer + start, length, offset); - return offset - original_offset; + WriteEncodedValueHeader(stream, type, length - 1); + stream->Write(buffer + start, length); + return stream->Tell() - original_offset; } -size_t DexWriter::WriteEncodedValueHeader(int8_t value_type, size_t value_arg, size_t offset) { +size_t DexWriter::WriteEncodedValueHeader(Stream* stream, int8_t value_type, size_t value_arg) { uint8_t buffer[1] = { static_cast<uint8_t>((value_arg << 5) | value_type) }; - return Write(buffer, sizeof(uint8_t), offset); + return stream->Write(buffer, sizeof(uint8_t)); } -size_t DexWriter::WriteEncodedArray(dex_ir::EncodedValueVector* values, size_t offset) { - size_t original_offset = offset; - offset += WriteUleb128(values->size(), offset); +size_t DexWriter::WriteEncodedArray(Stream* stream, dex_ir::EncodedValueVector* values) { + size_t original_offset = stream->Tell(); + stream->WriteUleb128(values->size()); for (std::unique_ptr<dex_ir::EncodedValue>& value : *values) { - offset += WriteEncodedValue(value.get(), offset); + WriteEncodedValue(stream, value.get()); } - return offset - original_offset; + return stream->Tell() - original_offset; } -size_t DexWriter::WriteEncodedAnnotation(dex_ir::EncodedAnnotation* annotation, size_t offset) { - size_t original_offset = offset; - offset += WriteUleb128(annotation->GetType()->GetIndex(), offset); - offset += WriteUleb128(annotation->GetAnnotationElements()->size(), offset); +size_t DexWriter::WriteEncodedAnnotation(Stream* stream, dex_ir::EncodedAnnotation* annotation) { + size_t original_offset = stream->Tell(); + stream->WriteUleb128(annotation->GetType()->GetIndex()); + stream->WriteUleb128(annotation->GetAnnotationElements()->size()); for (std::unique_ptr<dex_ir::AnnotationElement>& annotation_element : *annotation->GetAnnotationElements()) { - offset += WriteUleb128(annotation_element->GetName()->GetIndex(), offset); - offset += WriteEncodedValue(annotation_element->GetValue(), offset); + stream->WriteUleb128(annotation_element->GetName()->GetIndex()); + WriteEncodedValue(stream, annotation_element->GetValue()); } - return offset - original_offset; + return stream->Tell() - original_offset; } -size_t DexWriter::WriteEncodedFields(dex_ir::FieldItemVector* fields, size_t offset) { - size_t original_offset = offset; +size_t DexWriter::WriteEncodedFields(Stream* stream, dex_ir::FieldItemVector* fields) { + size_t original_offset = stream->Tell(); uint32_t prev_index = 0; for (std::unique_ptr<dex_ir::FieldItem>& field : *fields) { uint32_t index = field->GetFieldId()->GetIndex(); - offset += WriteUleb128(index - prev_index, offset); - offset += WriteUleb128(field->GetAccessFlags(), offset); + stream->WriteUleb128(index - prev_index); + stream->WriteUleb128(field->GetAccessFlags()); prev_index = index; } - return offset - original_offset; + return stream->Tell() - original_offset; } -size_t DexWriter::WriteEncodedMethods(dex_ir::MethodItemVector* methods, size_t offset) { - size_t original_offset = offset; +size_t DexWriter::WriteEncodedMethods(Stream* stream, dex_ir::MethodItemVector* methods) { + size_t original_offset = stream->Tell(); uint32_t prev_index = 0; for (std::unique_ptr<dex_ir::MethodItem>& method : *methods) { uint32_t index = method->GetMethodId()->GetIndex(); uint32_t code_off = method->GetCodeItem() == nullptr ? 0 : method->GetCodeItem()->GetOffset(); - offset += WriteUleb128(index - prev_index, offset); - offset += WriteUleb128(method->GetAccessFlags(), offset); - offset += WriteUleb128(code_off, offset); + stream->WriteUleb128(index - prev_index); + stream->WriteUleb128(method->GetAccessFlags()); + stream->WriteUleb128(code_off); prev_index = index; } - return offset - original_offset; + return stream->Tell() - original_offset; } // TODO: Refactor this to remove duplicated boiler plate. One way to do this is adding // function that takes a CollectionVector<T> and uses overloading. -uint32_t DexWriter::WriteStringIds(uint32_t offset, bool reserve_only) { - const uint32_t start = offset; +uint32_t DexWriter::WriteStringIds(Stream* stream, bool reserve_only) { + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::StringId>& string_id : header_->GetCollections().StringIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeStringIdItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeStringIdItem)); if (reserve_only) { - offset += string_id->GetSize(); + stream->Skip(string_id->GetSize()); } else { uint32_t string_data_off = string_id->DataItem()->GetOffset(); - offset += Write(&string_data_off, string_id->GetSize(), offset); + stream->Write(&string_data_off, string_id->GetSize()); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetStringIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteStringDatas(uint32_t offset) { - const uint32_t start = offset; +uint32_t DexWriter::WriteStringDatas(Stream* stream) { + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::StringData>& string_data : header_->GetCollections().StringDatas()) { - ProcessOffset(&offset, string_data.get()); - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeStringDataItem)); - offset += WriteUleb128(CountModifiedUtf8Chars(string_data->Data()), offset); + ProcessOffset(stream, string_data.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeStringDataItem)); + stream->WriteUleb128(CountModifiedUtf8Chars(string_data->Data())); + stream->Write(string_data->Data(), strlen(string_data->Data())); // Skip null terminator (already zeroed out, no need to write). - offset += Write(string_data->Data(), strlen(string_data->Data()), offset) + 1u; + stream->Skip(1); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetStringDatasOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteTypeIds(uint32_t offset) { +uint32_t DexWriter::WriteTypeIds(Stream* stream) { uint32_t descriptor_idx[1]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::TypeId>& type_id : header_->GetCollections().TypeIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeTypeIdItem)); - ProcessOffset(&offset, type_id.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeTypeIdItem)); + ProcessOffset(stream, type_id.get()); descriptor_idx[0] = type_id->GetStringId()->GetIndex(); - offset += Write(descriptor_idx, type_id->GetSize(), offset); + stream->Write(descriptor_idx, type_id->GetSize()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetTypeIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteTypeLists(uint32_t offset) { +uint32_t DexWriter::WriteTypeLists(Stream* stream) { uint32_t size[1]; uint16_t list[1]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::TypeList>& type_list : header_->GetCollections().TypeLists()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeTypeList)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeTypeList)); size[0] = type_list->GetTypeList()->size(); - ProcessOffset(&offset, type_list.get()); - offset += Write(size, sizeof(uint32_t), offset); + ProcessOffset(stream, type_list.get()); + stream->Write(size, sizeof(uint32_t)); for (const dex_ir::TypeId* type_id : *type_list->GetTypeList()) { list[0] = type_id->GetIndex(); - offset += Write(list, sizeof(uint16_t), offset); + stream->Write(list, sizeof(uint16_t)); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetTypeListsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteProtoIds(uint32_t offset, bool reserve_only) { +uint32_t DexWriter::WriteProtoIds(Stream* stream, bool reserve_only) { uint32_t buffer[3]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::ProtoId>& proto_id : header_->GetCollections().ProtoIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeProtoIdItem)); - ProcessOffset(&offset, proto_id.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeProtoIdItem)); + ProcessOffset(stream, proto_id.get()); if (reserve_only) { - offset += proto_id->GetSize(); + stream->Skip(proto_id->GetSize()); } else { buffer[0] = proto_id->Shorty()->GetIndex(); buffer[1] = proto_id->ReturnType()->GetIndex(); buffer[2] = proto_id->Parameters() == nullptr ? 0 : proto_id->Parameters()->GetOffset(); - offset += Write(buffer, proto_id->GetSize(), offset); + stream->Write(buffer, proto_id->GetSize()); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetProtoIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteFieldIds(uint32_t offset) { +uint32_t DexWriter::WriteFieldIds(Stream* stream) { uint16_t buffer[4]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::FieldId>& field_id : header_->GetCollections().FieldIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeFieldIdItem)); - ProcessOffset(&offset, field_id.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeFieldIdItem)); + ProcessOffset(stream, field_id.get()); buffer[0] = field_id->Class()->GetIndex(); buffer[1] = field_id->Type()->GetIndex(); buffer[2] = field_id->Name()->GetIndex(); buffer[3] = field_id->Name()->GetIndex() >> 16; - offset += Write(buffer, field_id->GetSize(), offset); + stream->Write(buffer, field_id->GetSize()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetFieldIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteMethodIds(uint32_t offset) { +uint32_t DexWriter::WriteMethodIds(Stream* stream) { uint16_t buffer[4]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::MethodId>& method_id : header_->GetCollections().MethodIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeMethodIdItem)); - ProcessOffset(&offset, method_id.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeMethodIdItem)); + ProcessOffset(stream, method_id.get()); buffer[0] = method_id->Class()->GetIndex(); buffer[1] = method_id->Proto()->GetIndex(); buffer[2] = method_id->Name()->GetIndex(); buffer[3] = method_id->Name()->GetIndex() >> 16; - offset += Write(buffer, method_id->GetSize(), offset); + stream->Write(buffer, method_id->GetSize()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetMethodIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteEncodedArrays(uint32_t offset) { - const uint32_t start = offset; +uint32_t DexWriter::WriteEncodedArrays(Stream* stream) { + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::EncodedArrayItem>& encoded_array : header_->GetCollections().EncodedArrayItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeEncodedArrayItem)); - ProcessOffset(&offset, encoded_array.get()); - offset += WriteEncodedArray(encoded_array->GetEncodedValues(), offset); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeEncodedArrayItem)); + ProcessOffset(stream, encoded_array.get()); + WriteEncodedArray(stream, encoded_array->GetEncodedValues()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetEncodedArrayItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteAnnotations(uint32_t offset) { +uint32_t DexWriter::WriteAnnotations(Stream* stream) { uint8_t visibility[1]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::AnnotationItem>& annotation : header_->GetCollections().AnnotationItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeAnnotationItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeAnnotationItem)); visibility[0] = annotation->GetVisibility(); - ProcessOffset(&offset, annotation.get()); - offset += Write(visibility, sizeof(uint8_t), offset); - offset += WriteEncodedAnnotation(annotation->GetAnnotation(), offset); + ProcessOffset(stream, annotation.get()); + stream->Write(visibility, sizeof(uint8_t)); + WriteEncodedAnnotation(stream, annotation->GetAnnotation()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetAnnotationItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteAnnotationSets(uint32_t offset) { +uint32_t DexWriter::WriteAnnotationSets(Stream* stream) { uint32_t size[1]; uint32_t annotation_off[1]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::AnnotationSetItem>& annotation_set : header_->GetCollections().AnnotationSetItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeAnnotationSetItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeAnnotationSetItem)); size[0] = annotation_set->GetItems()->size(); - ProcessOffset(&offset, annotation_set.get()); - offset += Write(size, sizeof(uint32_t), offset); + ProcessOffset(stream, annotation_set.get()); + stream->Write(size, sizeof(uint32_t)); for (dex_ir::AnnotationItem* annotation : *annotation_set->GetItems()) { annotation_off[0] = annotation->GetOffset(); - offset += Write(annotation_off, sizeof(uint32_t), offset); + stream->Write(annotation_off, sizeof(uint32_t)); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetAnnotationSetItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteAnnotationSetRefs(uint32_t offset) { +uint32_t DexWriter::WriteAnnotationSetRefs(Stream* stream) { uint32_t size[1]; uint32_t annotations_off[1]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::AnnotationSetRefList>& annotation_set_ref : header_->GetCollections().AnnotationSetRefLists()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeAnnotationSetRefList)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeAnnotationSetRefList)); size[0] = annotation_set_ref->GetItems()->size(); - ProcessOffset(&offset, annotation_set_ref.get()); - offset += Write(size, sizeof(uint32_t), offset); + ProcessOffset(stream, annotation_set_ref.get()); + stream->Write(size, sizeof(uint32_t)); for (dex_ir::AnnotationSetItem* annotation_set : *annotation_set_ref->GetItems()) { annotations_off[0] = annotation_set == nullptr ? 0 : annotation_set->GetOffset(); - offset += Write(annotations_off, sizeof(uint32_t), offset); + stream->Write(annotations_off, sizeof(uint32_t)); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetAnnotationSetRefListsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteAnnotationsDirectories(uint32_t offset) { +uint32_t DexWriter::WriteAnnotationsDirectories(Stream* stream) { uint32_t directory_buffer[4]; uint32_t annotation_buffer[2]; - const uint32_t start = offset; + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::AnnotationsDirectoryItem>& annotations_directory : header_->GetCollections().AnnotationsDirectoryItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeAnnotationsDirectoryItem)); - ProcessOffset(&offset, annotations_directory.get()); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeAnnotationsDirectoryItem)); + ProcessOffset(stream, annotations_directory.get()); directory_buffer[0] = annotations_directory->GetClassAnnotation() == nullptr ? 0 : annotations_directory->GetClassAnnotation()->GetOffset(); directory_buffer[1] = annotations_directory->GetFieldAnnotations() == nullptr ? 0 : @@ -461,13 +449,13 @@ uint32_t DexWriter::WriteAnnotationsDirectories(uint32_t offset) { annotations_directory->GetMethodAnnotations()->size(); directory_buffer[3] = annotations_directory->GetParameterAnnotations() == nullptr ? 0 : annotations_directory->GetParameterAnnotations()->size(); - offset += Write(directory_buffer, 4 * sizeof(uint32_t), offset); + stream->Write(directory_buffer, 4 * sizeof(uint32_t)); if (annotations_directory->GetFieldAnnotations() != nullptr) { for (std::unique_ptr<dex_ir::FieldAnnotation>& field : *annotations_directory->GetFieldAnnotations()) { annotation_buffer[0] = field->GetFieldId()->GetIndex(); annotation_buffer[1] = field->GetAnnotationSetItem()->GetOffset(); - offset += Write(annotation_buffer, 2 * sizeof(uint32_t), offset); + stream->Write(annotation_buffer, 2 * sizeof(uint32_t)); } } if (annotations_directory->GetMethodAnnotations() != nullptr) { @@ -475,7 +463,7 @@ uint32_t DexWriter::WriteAnnotationsDirectories(uint32_t offset) { *annotations_directory->GetMethodAnnotations()) { annotation_buffer[0] = method->GetMethodId()->GetIndex(); annotation_buffer[1] = method->GetAnnotationSetItem()->GetOffset(); - offset += Write(annotation_buffer, 2 * sizeof(uint32_t), offset); + stream->Write(annotation_buffer, 2 * sizeof(uint32_t)); } } if (annotations_directory->GetParameterAnnotations() != nullptr) { @@ -483,37 +471,36 @@ uint32_t DexWriter::WriteAnnotationsDirectories(uint32_t offset) { *annotations_directory->GetParameterAnnotations()) { annotation_buffer[0] = parameter->GetMethodId()->GetIndex(); annotation_buffer[1] = parameter->GetAnnotations()->GetOffset(); - offset += Write(annotation_buffer, 2 * sizeof(uint32_t), offset); + stream->Write(annotation_buffer, 2 * sizeof(uint32_t)); } } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetAnnotationsDirectoryItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteDebugInfoItems(uint32_t offset) { - const uint32_t start = offset; +uint32_t DexWriter::WriteDebugInfoItems(Stream* stream) { + const uint32_t start = stream->Tell(); for (std::unique_ptr<dex_ir::DebugInfoItem>& debug_info : header_->GetCollections().DebugInfoItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeDebugInfoItem)); - ProcessOffset(&offset, debug_info.get()); - offset += Write(debug_info->GetDebugInfo(), debug_info->GetDebugInfoSize(), offset); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeDebugInfoItem)); + ProcessOffset(stream, debug_info.get()); + stream->Write(debug_info->GetDebugInfo(), debug_info->GetDebugInfoSize()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetDebugInfoItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteCodeItemPostInstructionData(dex_ir::CodeItem* code_item, - uint32_t offset, +uint32_t DexWriter::WriteCodeItemPostInstructionData(Stream* stream, + dex_ir::CodeItem* code_item, bool reserve_only) { - const uint32_t start_offset = offset; + const uint32_t start_offset = stream->Tell(); if (code_item->TriesSize() != 0) { - // Align for the try items. - offset = RoundUp(offset, DexFile::TryItem::kAlignment); + stream->AlignTo(DexFile::TryItem::kAlignment); // Write try items. for (std::unique_ptr<const dex_ir::TryItem>& try_item : *code_item->Tries()) { DexFile::TryItem disk_try_item; @@ -522,38 +509,37 @@ uint32_t DexWriter::WriteCodeItemPostInstructionData(dex_ir::CodeItem* code_item disk_try_item.insn_count_ = try_item->InsnCount(); disk_try_item.handler_off_ = try_item->GetHandlers()->GetListOffset(); } - offset += Write(&disk_try_item, sizeof(disk_try_item), offset); + stream->Write(&disk_try_item, sizeof(disk_try_item)); } - size_t max_offset = offset; // Leave offset pointing to the end of the try items. - UNUSED(WriteUleb128(code_item->Handlers()->size(), offset)); + const size_t offset = stream->Tell(); + size_t max_offset = offset + stream->WriteUleb128(code_item->Handlers()->size()); for (std::unique_ptr<const dex_ir::CatchHandler>& handlers : *code_item->Handlers()) { - size_t list_offset = offset + handlers->GetListOffset(); + stream->Seek(offset + handlers->GetListOffset()); uint32_t size = handlers->HasCatchAll() ? (handlers->GetHandlers()->size() - 1) * -1 : handlers->GetHandlers()->size(); - list_offset += WriteSleb128(size, list_offset); + stream->WriteSleb128(size); for (std::unique_ptr<const dex_ir::TypeAddrPair>& handler : *handlers->GetHandlers()) { if (handler->GetTypeId() != nullptr) { - list_offset += WriteUleb128(handler->GetTypeId()->GetIndex(), list_offset); + stream->WriteUleb128(handler->GetTypeId()->GetIndex()); } - list_offset += WriteUleb128(handler->GetAddress(), list_offset); + stream->WriteUleb128(handler->GetAddress()); } // TODO: Clean this up to write the handlers in address order. - max_offset = std::max(max_offset, list_offset); + max_offset = std::max(max_offset, stream->Tell()); } - offset = max_offset; + stream->Seek(max_offset); } - - return offset - start_offset; + return stream->Tell() - start_offset; } -uint32_t DexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, - uint32_t offset, +uint32_t DexWriter::WriteCodeItem(Stream* stream, + dex_ir::CodeItem* code_item, bool reserve_only) { DCHECK(code_item != nullptr); - const uint32_t start_offset = offset; - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeCodeItem)); - ProcessOffset(&offset, code_item); + const uint32_t start_offset = stream->Tell(); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeCodeItem)); + ProcessOffset(stream, code_item); StandardDexFile::CodeItem disk_code_item; if (!reserve_only) { @@ -568,50 +554,50 @@ uint32_t DexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, } // Avoid using sizeof so that we don't write the fake instruction array at the end of the code // item. - offset += Write(&disk_code_item, - OFFSETOF_MEMBER(StandardDexFile::CodeItem, insns_), - offset); + stream->Write(&disk_code_item, OFFSETOF_MEMBER(StandardDexFile::CodeItem, insns_)); // Write the instructions. - offset += Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t), offset); + stream->Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t)); // Write the post instruction data. - offset += WriteCodeItemPostInstructionData(code_item, offset, reserve_only); - return offset - start_offset; + WriteCodeItemPostInstructionData(stream, code_item, reserve_only); + if (reserve_only) { + stream->Clear(start_offset, stream->Tell() - start_offset); + } + return stream->Tell() - start_offset; } -uint32_t DexWriter::WriteCodeItems(uint32_t offset, bool reserve_only) { +uint32_t DexWriter::WriteCodeItems(Stream* stream, bool reserve_only) { DexLayoutSection* code_section = nullptr; if (!reserve_only && dex_layout_ != nullptr) { code_section = &dex_layout_->GetSections().sections_[static_cast<size_t>( DexLayoutSections::SectionType::kSectionTypeCode)]; } - uint32_t start = offset; + const uint32_t start = stream->Tell(); for (auto& code_item : header_->GetCollections().CodeItems()) { - const size_t code_item_size = WriteCodeItem(code_item.get(), offset, reserve_only); + const size_t code_item_size = WriteCodeItem(stream, code_item.get(), reserve_only); // Only add the section hotness info once. if (!reserve_only && code_section != nullptr) { auto it = dex_layout_->LayoutHotnessInfo().code_item_layout_.find(code_item.get()); if (it != dex_layout_->LayoutHotnessInfo().code_item_layout_.end()) { code_section->parts_[static_cast<size_t>(it->second)].CombineSection( - offset, - offset + code_item_size); + stream->Tell() - code_item_size, + stream->Tell()); } } - offset += code_item_size; } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetCodeItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteClassDefs(uint32_t offset, bool reserve_only) { - const uint32_t start = offset; +uint32_t DexWriter::WriteClassDefs(Stream* stream, bool reserve_only) { + const uint32_t start = stream->Tell(); uint32_t class_def_buffer[8]; for (std::unique_ptr<dex_ir::ClassDef>& class_def : header_->GetCollections().ClassDefs()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeClassDefItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeClassDefItem)); if (reserve_only) { - offset += class_def->GetSize(); + stream->Skip(class_def->GetSize()); } else { class_def_buffer[0] = class_def->ClassType()->GetIndex(); class_def_buffer[1] = class_def->GetAccessFlags(); @@ -626,94 +612,94 @@ uint32_t DexWriter::WriteClassDefs(uint32_t offset, bool reserve_only) { class_def->GetClassData()->GetOffset(); class_def_buffer[7] = class_def->StaticValues() == nullptr ? 0 : class_def->StaticValues()->GetOffset(); - offset += Write(class_def_buffer, class_def->GetSize(), offset); + stream->Write(class_def_buffer, class_def->GetSize()); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetClassDefsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteClassDatas(uint32_t offset) { - const uint32_t start = offset; +uint32_t DexWriter::WriteClassDatas(Stream* stream) { + const uint32_t start = stream->Tell(); for (const std::unique_ptr<dex_ir::ClassData>& class_data : header_->GetCollections().ClassDatas()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeClassDataItem)); - ProcessOffset(&offset, class_data.get()); - offset += WriteUleb128(class_data->StaticFields()->size(), offset); - offset += WriteUleb128(class_data->InstanceFields()->size(), offset); - offset += WriteUleb128(class_data->DirectMethods()->size(), offset); - offset += WriteUleb128(class_data->VirtualMethods()->size(), offset); - offset += WriteEncodedFields(class_data->StaticFields(), offset); - offset += WriteEncodedFields(class_data->InstanceFields(), offset); - offset += WriteEncodedMethods(class_data->DirectMethods(), offset); - offset += WriteEncodedMethods(class_data->VirtualMethods(), offset); - } - if (compute_offsets_ && start != offset) { + stream->AlignTo(SectionAlignment(DexFile::kDexTypeClassDataItem)); + ProcessOffset(stream, class_data.get()); + stream->WriteUleb128(class_data->StaticFields()->size()); + stream->WriteUleb128(class_data->InstanceFields()->size()); + stream->WriteUleb128(class_data->DirectMethods()->size()); + stream->WriteUleb128(class_data->VirtualMethods()->size()); + WriteEncodedFields(stream, class_data->StaticFields()); + WriteEncodedFields(stream, class_data->InstanceFields()); + WriteEncodedMethods(stream, class_data->DirectMethods()); + WriteEncodedMethods(stream, class_data->VirtualMethods()); + } + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetClassDatasOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteCallSiteIds(uint32_t offset, bool reserve_only) { - const uint32_t start = offset; +uint32_t DexWriter::WriteCallSiteIds(Stream* stream, bool reserve_only) { + const uint32_t start = stream->Tell(); uint32_t call_site_off[1]; for (std::unique_ptr<dex_ir::CallSiteId>& call_site_id : header_->GetCollections().CallSiteIds()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeCallSiteIdItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeCallSiteIdItem)); if (reserve_only) { - offset += call_site_id->GetSize(); + stream->Skip(call_site_id->GetSize()); } else { call_site_off[0] = call_site_id->CallSiteItem()->GetOffset(); - offset += Write(call_site_off, call_site_id->GetSize(), offset); + stream->Write(call_site_off, call_site_id->GetSize()); } } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetCallSiteIdsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteMethodHandles(uint32_t offset) { - const uint32_t start = offset; +uint32_t DexWriter::WriteMethodHandles(Stream* stream) { + const uint32_t start = stream->Tell(); uint16_t method_handle_buff[4]; for (std::unique_ptr<dex_ir::MethodHandleItem>& method_handle : header_->GetCollections().MethodHandleItems()) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeMethodHandleItem)); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeMethodHandleItem)); method_handle_buff[0] = static_cast<uint16_t>(method_handle->GetMethodHandleType()); method_handle_buff[1] = 0; // unused. method_handle_buff[2] = method_handle->GetFieldOrMethodId()->GetIndex(); method_handle_buff[3] = 0; // unused. - offset += Write(method_handle_buff, method_handle->GetSize(), offset); + stream->Write(method_handle_buff, method_handle->GetSize()); } - if (compute_offsets_ && start != offset) { + if (compute_offsets_ && start != stream->Tell()) { header_->GetCollections().SetMethodHandleItemsOffset(start); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::WriteMapItems(uint32_t offset, MapItemQueue* queue) { +uint32_t DexWriter::WriteMapItems(Stream* stream, MapItemQueue* queue) { // All the sections should already have been added. uint16_t uint16_buffer[2]; uint32_t uint32_buffer[2]; uint16_buffer[1] = 0; uint32_buffer[0] = queue->size(); - const uint32_t start = offset; - offset += Write(uint32_buffer, sizeof(uint32_t), offset); + const uint32_t start = stream->Tell(); + stream->Write(uint32_buffer, sizeof(uint32_t)); while (!queue->empty()) { const MapItem& map_item = queue->top(); uint16_buffer[0] = map_item.type_; uint32_buffer[0] = map_item.size_; uint32_buffer[1] = map_item.offset_; - offset += Write(uint16_buffer, 2 * sizeof(uint16_t), offset); - offset += Write(uint32_buffer, 2 * sizeof(uint32_t), offset); + stream->Write(uint16_buffer, 2 * sizeof(uint16_t)); + stream->Write(uint32_buffer, 2 * sizeof(uint32_t)); queue->pop(); } - return offset - start; + return stream->Tell() - start; } -uint32_t DexWriter::GenerateAndWriteMapItems(uint32_t offset) { +uint32_t DexWriter::GenerateAndWriteMapItems(Stream* stream) { dex_ir::Collections& collection = header_->GetCollections(); MapItemQueue queue; @@ -777,10 +763,10 @@ uint32_t DexWriter::GenerateAndWriteMapItems(uint32_t offset) { collection.AnnotationsDirectoryItemsOffset())); // Write the map items. - return WriteMapItems(offset, &queue); + return WriteMapItems(stream, &queue); } -void DexWriter::WriteHeader() { +void DexWriter::WriteHeader(Stream* stream) { StandardDexFile::Header header; if (CompactDexFile::IsMagicValid(header_->Magic())) { StandardDexFile::WriteMagic(header.magic_); @@ -818,78 +804,97 @@ void DexWriter::WriteHeader() { CHECK_EQ(sizeof(header), GetHeaderSize()); static_assert(sizeof(header) == 0x70, "Size doesn't match dex spec"); - UNUSED(Write(reinterpret_cast<uint8_t*>(&header), sizeof(header), 0u)); + stream->Seek(0); + stream->Overwrite(reinterpret_cast<uint8_t*>(&header), sizeof(header)); } size_t DexWriter::GetHeaderSize() const { return sizeof(StandardDexFile::Header); } -void DexWriter::WriteMemMap() { +void DexWriter::Write(DexContainer* output) { + Stream stream_storage(output->GetMainSection()); + Stream* stream = &stream_storage; + // Starting offset is right after the header. - uint32_t offset = GetHeaderSize(); + stream->Seek(GetHeaderSize()); dex_ir::Collections& collection = header_->GetCollections(); // Based on: https://source.android.com/devices/tech/dalvik/dex-format // Since the offsets may not be calculated already, the writing must be done in the correct order. - const uint32_t string_ids_offset = offset; - offset += WriteStringIds(offset, /*reserve_only*/ true); - offset += WriteTypeIds(offset); - const uint32_t proto_ids_offset = offset; - offset += WriteProtoIds(offset, /*reserve_only*/ true); - offset += WriteFieldIds(offset); - offset += WriteMethodIds(offset); - const uint32_t class_defs_offset = offset; - offset += WriteClassDefs(offset, /*reserve_only*/ true); - const uint32_t call_site_ids_offset = offset; - offset += WriteCallSiteIds(offset, /*reserve_only*/ true); - offset += WriteMethodHandles(offset); + const uint32_t string_ids_offset = stream->Tell(); + WriteStringIds(stream, /*reserve_only*/ true); + WriteTypeIds(stream); + const uint32_t proto_ids_offset = stream->Tell(); + WriteProtoIds(stream, /*reserve_only*/ true); + WriteFieldIds(stream); + WriteMethodIds(stream); + const uint32_t class_defs_offset = stream->Tell(); + WriteClassDefs(stream, /*reserve_only*/ true); + const uint32_t call_site_ids_offset = stream->Tell(); + WriteCallSiteIds(stream, /*reserve_only*/ true); + WriteMethodHandles(stream); uint32_t data_offset_ = 0u; if (compute_offsets_) { // Data section. - offset = RoundUp(offset, kDataSectionAlignment); - data_offset_ = offset; + stream->AlignTo(kDataSectionAlignment); + data_offset_ = stream->Tell(); } // Write code item first to minimize the space required for encoded methods. // Reserve code item space since we need the debug offsets to actually write them. - const uint32_t code_items_offset = offset; - offset += WriteCodeItems(offset, /*reserve_only*/ true); + const uint32_t code_items_offset = stream->Tell(); + WriteCodeItems(stream, /*reserve_only*/ true); // Write debug info section. - offset += WriteDebugInfoItems(offset); - // Actually write code items since debug info offsets are calculated now. - WriteCodeItems(code_items_offset, /*reserve_only*/ false); - - offset += WriteEncodedArrays(offset); - offset += WriteAnnotations(offset); - offset += WriteAnnotationSets(offset); - offset += WriteAnnotationSetRefs(offset); - offset += WriteAnnotationsDirectories(offset); - offset += WriteTypeLists(offset); - offset += WriteClassDatas(offset); - offset += WriteStringDatas(offset); + WriteDebugInfoItems(stream); + { + // Actually write code items since debug info offsets are calculated now. + Stream::ScopedSeek seek(stream, code_items_offset); + WriteCodeItems(stream, /*reserve_only*/ false); + } + + WriteEncodedArrays(stream); + WriteAnnotations(stream); + WriteAnnotationSets(stream); + WriteAnnotationSetRefs(stream); + WriteAnnotationsDirectories(stream); + WriteTypeLists(stream); + WriteClassDatas(stream); + WriteStringDatas(stream); // Write delayed id sections that depend on data sections. - WriteStringIds(string_ids_offset, /*reserve_only*/ false); - WriteProtoIds(proto_ids_offset, /*reserve_only*/ false); - WriteClassDefs(class_defs_offset, /*reserve_only*/ false); - WriteCallSiteIds(call_site_ids_offset, /*reserve_only*/ false); + { + Stream::ScopedSeek seek(stream, string_ids_offset); + WriteStringIds(stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(stream, proto_ids_offset); + WriteProtoIds(stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(stream, class_defs_offset); + WriteClassDefs(stream, /*reserve_only*/ false); + } + { + Stream::ScopedSeek seek(stream, call_site_ids_offset); + WriteCallSiteIds(stream, /*reserve_only*/ false); + } // Write the map list. if (compute_offsets_) { - offset = RoundUp(offset, SectionAlignment(DexFile::kDexTypeMapList)); - collection.SetMapListOffset(offset); + stream->AlignTo(SectionAlignment(DexFile::kDexTypeMapList)); + collection.SetMapListOffset(stream->Tell()); } else { - offset = collection.MapListOffset(); + stream->Seek(collection.MapListOffset()); } - offset += GenerateAndWriteMapItems(offset); - offset = RoundUp(offset, kDataSectionAlignment); + GenerateAndWriteMapItems(stream); + stream->AlignTo(kDataSectionAlignment); // Map items are included in the data section. if (compute_offsets_) { - header_->SetDataSize(offset - data_offset_); + header_->SetDataSize(stream->Tell() - data_offset_); if (header_->DataSize() != 0) { // Offset must be zero when the size is zero. header_->SetDataOffset(data_offset_); @@ -903,37 +908,45 @@ void DexWriter::WriteMemMap() { if (link_data.size() > 0) { CHECK_EQ(header_->LinkSize(), static_cast<uint32_t>(link_data.size())); if (compute_offsets_) { - header_->SetLinkOffset(offset); + header_->SetLinkOffset(stream->Tell()); + } else { + stream->Seek(header_->LinkOffset()); } - offset += Write(&link_data[0], link_data.size(), header_->LinkOffset()); + stream->Write(&link_data[0], link_data.size()); } // Write header last. if (compute_offsets_) { - header_->SetFileSize(offset); + header_->SetFileSize(stream->Tell()); } - WriteHeader(); + WriteHeader(stream); if (dex_layout_->GetOptions().update_checksum_) { - header_->SetChecksum(DexFile::CalculateChecksum(mem_map_->Begin(), offset)); + header_->SetChecksum(DexFile::CalculateChecksum(stream->Begin(), header_->FileSize())); // Rewrite the header with the calculated checksum. - WriteHeader(); + WriteHeader(stream); } + + // Trim the map to make it sized as large as the dex file. + output->GetMainSection()->Resize(header_->FileSize()); } -void DexWriter::Output(dex_ir::Header* header, - MemMap* mem_map, - DexLayout* dex_layout, - bool compute_offsets, - CompactDexLevel compact_dex_level) { +void DexWriter::Output(DexLayout* dex_layout, + std::unique_ptr<DexContainer>* container, + bool compute_offsets) { CHECK(dex_layout != nullptr); std::unique_ptr<DexWriter> writer; - if (compact_dex_level != CompactDexLevel::kCompactDexLevelNone) { - writer.reset(new CompactDexWriter(header, mem_map, dex_layout, compact_dex_level)); + if (dex_layout->GetOptions().compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone) { + CHECK(compute_offsets) << "Compact dex requires computing offsets"; + writer.reset(new CompactDexWriter(dex_layout)); } else { - writer.reset(new DexWriter(header, mem_map, dex_layout, compute_offsets)); + writer.reset(new DexWriter(dex_layout, compute_offsets)); + } + DCHECK(container != nullptr); + if (*container == nullptr) { + *container = writer->CreateDexContainer(); } - writer->WriteMemMap(); + writer->Write(container->get()); } void MapItemQueue::AddIfNotEmpty(const MapItem& item) { @@ -942,4 +955,17 @@ void MapItemQueue::AddIfNotEmpty(const MapItem& item) { } } +void DexWriter::ProcessOffset(Stream* stream, dex_ir::Item* item) { + if (compute_offsets_) { + item->SetOffset(stream->Tell()); + } else { + // Not computing offsets, just use the one in the item. + stream->Seek(item->GetOffset()); + } +} + +std::unique_ptr<DexContainer> DexWriter::CreateDexContainer() const { + return std::unique_ptr<DexContainer>(new DexWriter::Container); +} + } // namespace art diff --git a/dexlayout/dex_writer.h b/dexlayout/dex_writer.h index 892ea7414b..e581a8b61b 100644 --- a/dexlayout/dex_writer.h +++ b/dexlayout/dex_writer.h @@ -20,9 +20,11 @@ #define ART_DEXLAYOUT_DEX_WRITER_H_ #include <functional> +#include <memory> // For unique_ptr #include "base/unix_file/fd_file.h" #include "dex/compact_dex_level.h" +#include "dex_container.h" #include "dex/dex_file.h" #include "dex_ir.h" #include "mem_map.h" @@ -39,7 +41,7 @@ struct MapItem { // Not using DexFile::MapItemType since compact dex and standard dex file may have different // sections. MapItem() = default; - MapItem(uint32_t type, uint32_t size, uint32_t offset) + MapItem(uint32_t type, uint32_t size, size_t offset) : type_(type), size_(size), offset_(offset) { } // Sort by decreasing order since the priority_queue puts largest elements first. @@ -63,6 +65,114 @@ class DexWriter { static constexpr uint32_t kDataSectionAlignment = sizeof(uint32_t) * 2; static constexpr uint32_t kDexSectionWordAlignment = 4; + // Stream that writes into a dex container section. Do not have two streams pointing to the same + // backing storage as there may be invalidation of backing storage to resize the section. + // Random access stream (consider refactoring). + class Stream { + public: + explicit Stream(DexContainer::Section* section) : section_(section) { + SyncWithSection(); + } + + const uint8_t* Begin() const { + return data_; + } + + // Functions are not virtual (yet) for speed. + size_t Tell() const { + return position_; + } + + void Seek(size_t position) { + position_ = position; + } + + // Does not allow overwriting for bug prevention purposes. + ALWAYS_INLINE size_t Write(const void* buffer, size_t length) { + EnsureStorage(length); + for (size_t i = 0; i < length; ++i) { + DCHECK_EQ(data_[position_ + i], 0u); + } + memcpy(&data_[position_], buffer, length); + position_ += length; + return length; + } + + ALWAYS_INLINE size_t Overwrite(const void* buffer, size_t length) { + EnsureStorage(length); + memcpy(&data_[position_], buffer, length); + position_ += length; + return length; + } + + ALWAYS_INLINE size_t Clear(size_t position, size_t length) { + EnsureStorage(length); + memset(&data_[position], 0, length); + return length; + } + + ALWAYS_INLINE size_t WriteSleb128(int32_t value) { + EnsureStorage(8); + uint8_t* ptr = &data_[position_]; + const size_t len = EncodeSignedLeb128(ptr, value) - ptr; + position_ += len; + return len; + } + + ALWAYS_INLINE size_t WriteUleb128(uint32_t value) { + EnsureStorage(8); + uint8_t* ptr = &data_[position_]; + const size_t len = EncodeUnsignedLeb128(ptr, value) - ptr; + position_ += len; + return len; + } + + ALWAYS_INLINE void AlignTo(const size_t alignment) { + position_ = RoundUp(position_, alignment); + } + + ALWAYS_INLINE void Skip(const size_t count) { + position_ += count; + } + + class ScopedSeek { + public: + ScopedSeek(Stream* stream, uint32_t offset) : stream_(stream), offset_(stream->Tell()) { + stream->Seek(offset); + } + + ~ScopedSeek() { + stream_->Seek(offset_); + } + + private: + Stream* const stream_; + const uint32_t offset_; + }; + + private: + ALWAYS_INLINE void EnsureStorage(size_t length) { + size_t end = position_ + length; + while (UNLIKELY(end > data_size_)) { + section_->Resize(data_size_ * 3 / 2 + 1); + SyncWithSection(); + } + } + + void SyncWithSection() { + data_ = section_->Begin(); + data_size_ = section_->Size(); + } + + // Current position of the stream. + size_t position_ = 0u; + DexContainer::Section* const section_ = nullptr; + // Cached Begin() from the container to provide faster accesses. + uint8_t* data_ = nullptr; + // Cached Size from the container to provide faster accesses. + size_t data_size_ = 0u; + }; + static inline constexpr uint32_t SectionAlignment(DexFile::MapItemType type) { switch (type) { case DexFile::kDexTypeClassDataItem: @@ -78,83 +188,85 @@ class DexWriter { } } - DexWriter(dex_ir::Header* header, - MemMap* mem_map, - DexLayout* dex_layout, - bool compute_offsets) - : header_(header), - mem_map_(mem_map), - dex_layout_(dex_layout), - compute_offsets_(compute_offsets) {} - - static void Output(dex_ir::Header* header, - MemMap* mem_map, - DexLayout* dex_layout, - bool compute_offsets, - CompactDexLevel compact_dex_level); + class Container : public DexContainer { + public: + Section* GetMainSection() OVERRIDE { + return &main_section_; + } + + Section* GetDataSection() OVERRIDE { + return &data_section_; + } + + bool IsCompactDexContainer() const OVERRIDE { + return false; + } + + private: + VectorSection main_section_; + VectorSection data_section_; + + friend class CompactDexWriter; + }; + + DexWriter(DexLayout* dex_layout, bool compute_offsets); + + static void Output(DexLayout* dex_layout, + std::unique_ptr<DexContainer>* container, + bool compute_offsets); virtual ~DexWriter() {} protected: - virtual void WriteMemMap(); - - size_t Write(const void* buffer, size_t length, size_t offset) WARN_UNUSED; - size_t WriteSleb128(uint32_t value, size_t offset) WARN_UNUSED; - size_t WriteUleb128(uint32_t value, size_t offset) WARN_UNUSED; - size_t WriteEncodedValue(dex_ir::EncodedValue* encoded_value, size_t offset) WARN_UNUSED; - size_t WriteEncodedValueHeader(int8_t value_type, size_t value_arg, size_t offset) WARN_UNUSED; - size_t WriteEncodedArray(dex_ir::EncodedValueVector* values, size_t offset) WARN_UNUSED; - size_t WriteEncodedAnnotation(dex_ir::EncodedAnnotation* annotation, size_t offset) WARN_UNUSED; - size_t WriteEncodedFields(dex_ir::FieldItemVector* fields, size_t offset) WARN_UNUSED; - size_t WriteEncodedMethods(dex_ir::MethodItemVector* methods, size_t offset) WARN_UNUSED; + virtual void Write(DexContainer* output); + virtual std::unique_ptr<DexContainer> CreateDexContainer() const; + + size_t WriteEncodedValue(Stream* stream, dex_ir::EncodedValue* encoded_value); + size_t WriteEncodedValueHeader(Stream* stream, int8_t value_type, size_t value_arg); + size_t WriteEncodedArray(Stream* stream, dex_ir::EncodedValueVector* values); + size_t WriteEncodedAnnotation(Stream* stream, dex_ir::EncodedAnnotation* annotation); + size_t WriteEncodedFields(Stream* stream, dex_ir::FieldItemVector* fields); + size_t WriteEncodedMethods(Stream* stream, dex_ir::MethodItemVector* methods); // Header and id section - virtual void WriteHeader(); + virtual void WriteHeader(Stream* stream); virtual size_t GetHeaderSize() const; // reserve_only means don't write, only reserve space. This is required since the string data // offsets must be assigned. - uint32_t WriteStringIds(uint32_t offset, bool reserve_only); - uint32_t WriteTypeIds(uint32_t offset); - uint32_t WriteProtoIds(uint32_t offset, bool reserve_only); - uint32_t WriteFieldIds(uint32_t offset); - uint32_t WriteMethodIds(uint32_t offset); - uint32_t WriteClassDefs(uint32_t offset, bool reserve_only); - uint32_t WriteCallSiteIds(uint32_t offset, bool reserve_only); - - uint32_t WriteEncodedArrays(uint32_t offset); - uint32_t WriteAnnotations(uint32_t offset); - uint32_t WriteAnnotationSets(uint32_t offset); - uint32_t WriteAnnotationSetRefs(uint32_t offset); - uint32_t WriteAnnotationsDirectories(uint32_t offset); + uint32_t WriteStringIds(Stream* stream, bool reserve_only); + uint32_t WriteTypeIds(Stream* stream); + uint32_t WriteProtoIds(Stream* stream, bool reserve_only); + uint32_t WriteFieldIds(Stream* stream); + uint32_t WriteMethodIds(Stream* stream); + uint32_t WriteClassDefs(Stream* stream, bool reserve_only); + uint32_t WriteCallSiteIds(Stream* stream, bool reserve_only); + + uint32_t WriteEncodedArrays(Stream* stream); + uint32_t WriteAnnotations(Stream* stream); + uint32_t WriteAnnotationSets(Stream* stream); + uint32_t WriteAnnotationSetRefs(Stream* stream); + uint32_t WriteAnnotationsDirectories(Stream* stream); // Data section. - uint32_t WriteDebugInfoItems(uint32_t offset); - uint32_t WriteCodeItems(uint32_t offset, bool reserve_only); - uint32_t WriteTypeLists(uint32_t offset); - uint32_t WriteStringDatas(uint32_t offset); - uint32_t WriteClassDatas(uint32_t offset); - uint32_t WriteMethodHandles(uint32_t offset); - uint32_t WriteMapItems(uint32_t offset, MapItemQueue* queue); - uint32_t GenerateAndWriteMapItems(uint32_t offset); - - virtual uint32_t WriteCodeItemPostInstructionData(dex_ir::CodeItem* item, - uint32_t offset, + uint32_t WriteDebugInfoItems(Stream* stream); + uint32_t WriteCodeItems(Stream* stream, bool reserve_only); + uint32_t WriteTypeLists(Stream* stream); + uint32_t WriteStringDatas(Stream* stream); + uint32_t WriteClassDatas(Stream* stream); + uint32_t WriteMethodHandles(Stream* stream); + uint32_t WriteMapItems(Stream* stream, MapItemQueue* queue); + uint32_t GenerateAndWriteMapItems(Stream* stream); + + virtual uint32_t WriteCodeItemPostInstructionData(Stream* stream, + dex_ir::CodeItem* item, bool reserve_only); - virtual uint32_t WriteCodeItem(dex_ir::CodeItem* item, uint32_t offset, bool reserve_only); + virtual uint32_t WriteCodeItem(Stream* stream, dex_ir::CodeItem* item, bool reserve_only); // Process an offset, if compute_offset is set, write into the dex ir item, otherwise read the // existing offset and use that for writing. - void ProcessOffset(uint32_t* const offset, dex_ir::Item* item) { - if (compute_offsets_) { - item->SetOffset(*offset); - } else { - // Not computing offsets, just use the one in the item. - *offset = item->GetOffset(); - } - } + void ProcessOffset(Stream* stream, dex_ir::Item* item); dex_ir::Header* const header_; - MemMap* const mem_map_; DexLayout* const dex_layout_; bool compute_offsets_; diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 3d3b121190..d33a0bde03 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1813,21 +1813,14 @@ void DexLayout::LayoutOutputFile(const DexFile* dex_file) { LayoutCodeItems(dex_file); } -void DexLayout::OutputDexFile(const DexFile* dex_file, bool compute_offsets) { - const std::string& dex_file_location = dex_file->GetLocation(); +void DexLayout::OutputDexFile(const DexFile* input_dex_file, + bool compute_offsets, + std::unique_ptr<DexContainer>* dex_container) { + const std::string& dex_file_location = input_dex_file->GetLocation(); std::string error_msg; std::unique_ptr<File> new_file; - // Since we allow dex growth, we need to size the map larger than the original input to be safe. - // Reserve an extra 10% to add some buffer room. Note that this is probably more than - // necessary. - static constexpr size_t kReserveFraction = 10; - // Add an extra constant amount since the compact dex header and extra tables may cause more - // expansion than fits in the reserve fraction for small dex files. - // TODO: Move to using a resizable buffer like a vector. - static constexpr size_t kExtraReserve = 128 * KB; - const size_t max_size = header_->FileSize() + kExtraReserve + - header_->FileSize() / kReserveFraction; - if (!options_.output_to_memmap_) { + // If options_.output_dex_directory_ is non null, we are outputting to a file. + if (options_.output_dex_directory_ != nullptr) { std::string output_location(options_.output_dex_directory_); size_t last_slash = dex_file_location.rfind('/'); std::string dex_file_directory = dex_file_location.substr(0, last_slash + 1); @@ -1843,31 +1836,17 @@ void DexLayout::OutputDexFile(const DexFile* dex_file, bool compute_offsets) { LOG(ERROR) << "Could not create dex writer output file: " << output_location; return; } - if (ftruncate(new_file->Fd(), max_size) != 0) { - LOG(ERROR) << "Could not grow dex writer output file: " << output_location;; - new_file->Erase(); - return; - } - mem_map_.reset(MemMap::MapFile(max_size, PROT_READ | PROT_WRITE, MAP_SHARED, - new_file->Fd(), 0, /*low_4gb*/ false, output_location.c_str(), &error_msg)); - } else { - mem_map_.reset(MemMap::MapAnonymous("layout dex", nullptr, max_size, - PROT_READ | PROT_WRITE, /* low_4gb */ false, /* reuse */ false, &error_msg)); - } - if (mem_map_ == nullptr) { - LOG(ERROR) << "Could not create mem map for dex writer output: " << error_msg; - if (new_file != nullptr) { - new_file->Erase(); - } - return; } - DexWriter::Output(header_, mem_map_.get(), this, compute_offsets, options_.compact_dex_level_); + DexWriter::Output(this, dex_container, compute_offsets); + DexContainer* const container = dex_container->get(); + DexContainer::Section* const main_section = container->GetMainSection(); + DexContainer::Section* const data_section = container->GetDataSection(); + CHECK_EQ(data_section->Size(), 0u) << "Unsupported"; if (new_file != nullptr) { - // Since we make the memmap larger than needed, shrink the file back down to not leave extra - // padding. - int res = new_file->SetLength(header_->FileSize()); - if (res != 0) { - LOG(ERROR) << "Truncating file resulted in " << res; + if (!new_file->WriteFully(main_section->Begin(), main_section->Size())) { + LOG(ERROR) << "Failed tow write dex file to " << dex_file_location; + new_file->Erase(); + return; } UNUSED(new_file->FlushCloseOrErase()); } @@ -1878,8 +1857,11 @@ void DexLayout::OutputDexFile(const DexFile* dex_file, bool compute_offsets) { */ void DexLayout::ProcessDexFile(const char* file_name, const DexFile* dex_file, - size_t dex_file_index) { - const bool output = options_.output_dex_directory_ != nullptr || options_.output_to_memmap_; + size_t dex_file_index, + std::unique_ptr<DexContainer>* dex_container) { + const bool has_output_container = dex_container != nullptr; + const bool output = options_.output_dex_directory_ != nullptr || has_output_container; + // Try to avoid eagerly assigning offsets to find bugs since GetOffset will abort if the offset // is unassigned. bool eagerly_assign_offsets = false; @@ -1918,22 +1900,29 @@ void DexLayout::ProcessDexFile(const char* file_name, if (do_layout) { LayoutOutputFile(dex_file); } + // The output needs a dex container, use a temporary one. + std::unique_ptr<DexContainer> temp_container; + if (dex_container == nullptr) { + dex_container = &temp_container; + } // If we didn't set the offsets eagerly, we definitely need to compute them here. - OutputDexFile(dex_file, do_layout || !eagerly_assign_offsets); + OutputDexFile(dex_file, do_layout || !eagerly_assign_offsets, dex_container); // Clear header before verifying to reduce peak RAM usage. const size_t file_size = header_->FileSize(); header.reset(); // Verify the output dex file's structure, only enabled by default for debug builds. - if (options_.verify_output_) { + if (options_.verify_output_ && has_output_container) { std::string error_msg; std::string location = "memory mapped file for " + std::string(file_name); // Dex file verifier cannot handle compact dex. bool verify = options_.compact_dex_level_ == CompactDexLevel::kCompactDexLevelNone; const ArtDexFileLoader dex_file_loader; + DexContainer::Section* section = (*dex_container)->GetMainSection(); + DCHECK_EQ(file_size, section->Size()); std::unique_ptr<const DexFile> output_dex_file( - dex_file_loader.Open(mem_map_->Begin(), + dex_file_loader.Open(section->Begin(), file_size, location, /* checksum */ 0, @@ -1988,7 +1977,8 @@ int DexLayout::ProcessFile(const char* file_name) { fprintf(out_file_, "Checksum verified\n"); } else { for (size_t i = 0; i < dex_files.size(); i++) { - ProcessDexFile(file_name, dex_files[i].get(), i); + // Pass in a null container to avoid output by default. + ProcessDexFile(file_name, dex_files[i].get(), i, /*dex_container*/ nullptr); } } return 0; diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h index cb0eabc7db..00d24dbda4 100644 --- a/dexlayout/dexlayout.h +++ b/dexlayout/dexlayout.h @@ -28,6 +28,7 @@ #include <unordered_map> #include "dex/compact_dex_level.h" +#include "dex_container.h" #include "dex/dex_file_layout.h" #include "dex_ir.h" #include "mem_map.h" @@ -55,7 +56,7 @@ class Options { bool disassemble_ = false; bool exports_only_ = false; bool ignore_bad_checksum_ = false; - bool output_to_memmap_ = false; + bool output_to_container_ = false; bool show_annotations_ = false; bool show_file_headers_ = false; bool show_section_headers_ = false; @@ -82,6 +83,18 @@ class DexLayoutHotnessInfo { class DexLayout { public: + class VectorOutputContainer { + public: + // Begin is not necessarily aligned (for now). + uint8_t* Begin() { + return &data_[0]; + } + + private: + std::vector<uint8_t> data_; + }; + + // Setting this to false disables class def layout entirely, which is stronger than strictly // necessary to ensure the partial order w.r.t. class derivation. TODO: Re-enable (b/68317550). static constexpr bool kChangeClassDefOrder = false; @@ -89,18 +102,21 @@ class DexLayout { DexLayout(Options& options, ProfileCompilationInfo* info, FILE* out_file, - dex_ir::Header* - header = nullptr) - : options_(options), info_(info), out_file_(out_file), header_(header) { } + dex_ir::Header* header) + : options_(options), + info_(info), + out_file_(out_file), + header_(header) { } int ProcessFile(const char* file_name); - void ProcessDexFile(const char* file_name, const DexFile* dex_file, size_t dex_file_index); + void ProcessDexFile(const char* file_name, + const DexFile* dex_file, + size_t dex_file_index, + std::unique_ptr<DexContainer>* dex_container); dex_ir::Header* GetHeader() const { return header_; } void SetHeader(dex_ir::Header* header) { header_ = header; } - MemMap* GetAndReleaseMemMap() { return mem_map_.release(); } - DexLayoutSections& GetSections() { return dex_sections_; } @@ -150,7 +166,9 @@ class DexLayout { // Creates a new layout for the dex file based on profile info. // Currently reorders ClassDefs, ClassDataItems, and CodeItems. void LayoutOutputFile(const DexFile* dex_file); - void OutputDexFile(const DexFile* dex_file, bool compute_offsets); + void OutputDexFile(const DexFile* input_dex_file, + bool compute_offsets, + std::unique_ptr<DexContainer>* dex_container); void DumpCFG(const DexFile* dex_file, int idx); void DumpCFG(const DexFile* dex_file, uint32_t dex_method_idx, const DexFile::CodeItem* code); @@ -159,7 +177,6 @@ class DexLayout { ProfileCompilationInfo* info_; FILE* out_file_; dex_ir::Header* header_; - std::unique_ptr<MemMap> mem_map_; DexLayoutSections dex_sections_; // Layout hotness information is only calculated when dexlayout is enabled. DexLayoutHotnessInfo layout_hotness_info_; diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc index 83fb99a734..ece0f932ce 100644 --- a/dexlayout/dexlayout_main.cc +++ b/dexlayout/dexlayout_main.cc @@ -80,7 +80,7 @@ int DexlayoutDriver(int argc, char** argv) { // Parse all arguments. while (1) { - const int ic = getopt(argc, argv, "abcdefghil:mo:p:stvw:x:"); + const int ic = getopt(argc, argv, "abcdefghil:o:p:stvw:x:"); if (ic < 0) { break; // done } @@ -119,9 +119,6 @@ int DexlayoutDriver(int argc, char** argv) { want_usage = true; } break; - case 'm': // output dex files to a memmap - options.output_to_memmap_ = true; - break; case 'o': // output file options.output_file_name_ = optarg; break; @@ -197,7 +194,7 @@ int DexlayoutDriver(int argc, char** argv) { } // Create DexLayout instance. - DexLayout dex_layout(options, profile_info.get(), out_file); + DexLayout dex_layout(options, profile_info.get(), out_file, /*header*/ nullptr); // Process all files supplied on command line. int result = 0; diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc index bedc4576d5..6d4b3e3e52 100644 --- a/dexoptanalyzer/dexoptanalyzer.cc +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -260,6 +260,7 @@ class DexoptAnalyzer FINAL { oat_file_assistant = std::make_unique<OatFileAssistant>(dex_file_.c_str(), isa_, false /*load_executable*/, + false /*only_load_system_executable*/, vdex_fd_, oat_fd_, zip_fd_); diff --git a/dt_fd_forward/dt_fd_forward.cc b/dt_fd_forward/dt_fd_forward.cc index cf3088dc60..1cf31a75a5 100644 --- a/dt_fd_forward/dt_fd_forward.cc +++ b/dt_fd_forward/dt_fd_forward.cc @@ -162,7 +162,7 @@ IOResult FdForwardTransport::ReadFullyWithoutChecks(void* data, size_t ndata) { IOResult FdForwardTransport::ReadUpToMax(void* data, size_t ndata, /*out*/size_t* read_amount) { CHECK_GE(read_fd_.get(), 0); int avail; - int res = ioctl(read_fd_, FIONREAD, &avail); + int res = TEMP_FAILURE_RETRY(ioctl(read_fd_, FIONREAD, &avail)); if (res < 0) { DT_IO_ERROR("Failed ioctl(read_fd_, FIONREAD, &avail)"); return IOResult::kError; @@ -172,7 +172,7 @@ IOResult FdForwardTransport::ReadUpToMax(void* data, size_t ndata, /*out*/size_t if (*read_amount == 0) { // Check if the read would cause an EOF. struct pollfd pollfd = { read_fd_, POLLRDHUP, 0 }; - res = poll(&pollfd, /*nfds*/1, /*timeout*/0); + res = TEMP_FAILURE_RETRY(poll(&pollfd, /*nfds*/1, /*timeout*/0)); if (res < 0 || (pollfd.revents & POLLERR) == POLLERR) { DT_IO_ERROR("Failed poll on read fd."); return IOResult::kError; @@ -214,13 +214,13 @@ IOResult FdForwardTransport::ReadFully(void* data, size_t ndata) { // No more data. Sleep without locks until more is available. We don't actually check for any // errors since possible ones are (1) the read_fd_ is closed or wakeup happens which are both // fine since the wakeup_fd_ or the poll failing will wake us up. - int poll_res = poll(pollfds, 2, -1); + int poll_res = TEMP_FAILURE_RETRY(poll(pollfds, 2, -1)); if (poll_res < 0) { DT_IO_ERROR("Failed to poll!"); } // Clear the wakeup_fd regardless. uint64_t val; - int unused = read(wakeup_fd_, &val, sizeof(val)); + int unused = TEMP_FAILURE_RETRY(read(wakeup_fd_, &val, sizeof(val))); DCHECK(unused == sizeof(val) || errno == EAGAIN); if (poll_res < 0) { return IOResult::kError; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index fcbf2f1c0a..f2a69f3bb3 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -1055,14 +1055,19 @@ class OatDumper { os << StringPrintf("checksum: 0x%08x\n", oat_dex_file.GetDexFileLocationChecksum()); const uint8_t* const oat_file_begin = oat_dex_file.GetOatFile()->Begin(); - const uint8_t* const vdex_file_begin = oat_dex_file.GetOatFile()->DexBegin(); - - // Print data range of the dex file embedded inside the corresponding vdex file. - const uint8_t* const dex_file_pointer = oat_dex_file.GetDexFilePointer(); - uint32_t dex_offset = dchecked_integral_cast<uint32_t>(dex_file_pointer - vdex_file_begin); - os << StringPrintf("dex-file: 0x%08x..0x%08x\n", - dex_offset, - dchecked_integral_cast<uint32_t>(dex_offset + oat_dex_file.FileSize() - 1)); + if (oat_dex_file.GetOatFile()->ContainsDexCode()) { + const uint8_t* const vdex_file_begin = oat_dex_file.GetOatFile()->DexBegin(); + + // Print data range of the dex file embedded inside the corresponding vdex file. + const uint8_t* const dex_file_pointer = oat_dex_file.GetDexFilePointer(); + uint32_t dex_offset = dchecked_integral_cast<uint32_t>(dex_file_pointer - vdex_file_begin); + os << StringPrintf( + "dex-file: 0x%08x..0x%08x\n", + dex_offset, + dchecked_integral_cast<uint32_t>(dex_offset + oat_dex_file.FileSize() - 1)); + } else { + os << StringPrintf("dex-file not in VDEX file\n"); + } // Create the dex file early. A lot of print-out things depend on it. std::string error_msg; @@ -3041,8 +3046,15 @@ static int DumpOatWithoutRuntime(OatFile* oat_file, OatDumperOptions* options, s return (success) ? EXIT_SUCCESS : EXIT_FAILURE; } -static int DumpOat(Runtime* runtime, const char* oat_filename, OatDumperOptions* options, +static int DumpOat(Runtime* runtime, + const char* oat_filename, + const char* dex_filename, + OatDumperOptions* options, std::ostream* os) { + if (dex_filename == nullptr) { + LOG(WARNING) << "No dex filename provided, " + << "oatdump might fail if the oat file does not contain the dex code."; + } std::string error_msg; std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename, oat_filename, @@ -3050,7 +3062,7 @@ static int DumpOat(Runtime* runtime, const char* oat_filename, OatDumperOptions* nullptr, false, /*low_4gb*/false, - nullptr, + dex_filename, &error_msg)); if (oat_file == nullptr) { fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str()); @@ -3064,7 +3076,10 @@ static int DumpOat(Runtime* runtime, const char* oat_filename, OatDumperOptions* } } -static int SymbolizeOat(const char* oat_filename, std::string& output_name, bool no_bits) { +static int SymbolizeOat(const char* oat_filename, + const char* dex_filename, + std::string& output_name, + bool no_bits) { std::string error_msg; std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_filename, oat_filename, @@ -3072,7 +3087,7 @@ static int SymbolizeOat(const char* oat_filename, std::string& output_name, bool nullptr, false, /*low_4gb*/false, - nullptr, + dex_filename, &error_msg)); if (oat_file == nullptr) { fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str()); @@ -3102,7 +3117,8 @@ class IMTDumper { static bool Dump(Runtime* runtime, const std::string& imt_file, bool dump_imt_stats, - const char* oat_filename) { + const char* oat_filename, + const char* dex_filename) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); @@ -3118,7 +3134,7 @@ class IMTDumper { nullptr, false, /*low_4gb*/false, - nullptr, + dex_filename, &error_msg)); if (oat_file == nullptr) { fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str()); @@ -3553,6 +3569,8 @@ struct OatdumpArgs : public CmdlineArgs { if (option.starts_with("--oat-file=")) { oat_filename_ = option.substr(strlen("--oat-file=")).data(); + } else if (option.starts_with("--dex-file=")) { + dex_filename_ = option.substr(strlen("--dex-file=")).data(); } else if (option.starts_with("--image=")) { image_location_ = option.substr(strlen("--image=")).data(); } else if (option == "--no-dump:vmap") { @@ -3704,6 +3722,7 @@ struct OatdumpArgs : public CmdlineArgs { public: const char* oat_filename_ = nullptr; + const char* dex_filename_ = nullptr; const char* class_filter_ = ""; const char* method_filter_ = ""; const char* image_location_ = nullptr; @@ -3764,10 +3783,12 @@ struct OatdumpMain : public CmdlineMain<OatdumpArgs> { // This is what "strip --only-keep-debug" does when it creates separate ELF file // with only debug data. We use it in similar way to exclude .rodata and .text. bool no_bits = args_->only_keep_debug_; - return SymbolizeOat(args_->oat_filename_, args_->output_name_, no_bits) == EXIT_SUCCESS; + return SymbolizeOat(args_->oat_filename_, args_->dex_filename_, args_->output_name_, no_bits) + == EXIT_SUCCESS; } else { return DumpOat(nullptr, args_->oat_filename_, + args_->dex_filename_, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS; } @@ -3780,12 +3801,14 @@ struct OatdumpMain : public CmdlineMain<OatdumpArgs> { return IMTDumper::Dump(runtime, args_->imt_dump_, args_->imt_stat_dump_, - args_->oat_filename_); + args_->oat_filename_, + args_->dex_filename_); } if (args_->oat_filename_ != nullptr) { return DumpOat(runtime, args_->oat_filename_, + args_->dex_filename_, oat_dumper_options_.get(), args_->os_) == EXIT_SUCCESS; } diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc index 53d84836fc..9e11a25e58 100644 --- a/openjdkjvmti/deopt_manager.cc +++ b/openjdkjvmti/deopt_manager.cc @@ -38,11 +38,11 @@ #include "base/enums.h" #include "base/mutex-inl.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc index dcc834abe9..323137aa5b 100644 --- a/openjdkjvmti/fixed_up_dex_file.cc +++ b/openjdkjvmti/fixed_up_dex_file.cc @@ -35,6 +35,7 @@ #include "dex/dex_file_loader.h" // Runtime includes. +#include "dex_container.h" #include "dex/compact_dex_level.h" #include "dex_to_dex_decompiler.h" #include "dexlayout.h" @@ -92,18 +93,21 @@ std::unique_ptr<FixedUpDexFile> FixedUpDexFile::Create(const art::DexFile& origi if (original.IsCompactDexFile()) { // Since we are supposed to return a standard dex, convert back using dexlayout. art::Options options; - options.output_to_memmap_ = true; options.compact_dex_level_ = art::CompactDexLevel::kCompactDexLevelNone; options.update_checksum_ = true; - art::DexLayout dex_layout(options, nullptr, nullptr); - dex_layout.ProcessDexFile(new_dex_file->GetLocation().c_str(), new_dex_file.get(), 0); - std::unique_ptr<art::MemMap> mem_map(dex_layout.GetAndReleaseMemMap()); - - const uint32_t dex_file_size = - reinterpret_cast<const art::DexFile::Header*>(mem_map->Begin())->file_size_; + art::DexLayout dex_layout(options, + /*info*/ nullptr, + /*out_file*/ nullptr, + /*header*/ nullptr); + std::unique_ptr<art::DexContainer> dex_container; + dex_layout.ProcessDexFile(new_dex_file->GetLocation().c_str(), + new_dex_file.get(), + 0, + &dex_container); + art::DexContainer::Section* main_section = dex_container->GetMainSection(); // Overwrite the dex file stored in data with the new result. data.clear(); - data.insert(data.end(), mem_map->Begin(), mem_map->Begin() + dex_file_size); + data.insert(data.end(), main_section->Begin(), main_section->End()); new_dex_file = dex_file_loader.Open( data.data(), data.size(), diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc index fa7a34401d..d5fffdf439 100644 --- a/openjdkjvmti/ti_breakpoint.cc +++ b/openjdkjvmti/ti_breakpoint.cc @@ -39,11 +39,11 @@ #include "base/mutex-inl.h" #include "deopt_manager.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" diff --git a/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc index db5c31c43d..c016966d21 100644 --- a/openjdkjvmti/ti_field.cc +++ b/openjdkjvmti/ti_field.cc @@ -35,9 +35,9 @@ #include "art_jvmti.h" #include "base/enums.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "jni_internal.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 57fb699435..3f144c8f0f 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -40,6 +40,7 @@ #include "dex/code_item_accessors-inl.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jit/jit.h" #include "jni_internal.h" @@ -47,7 +48,6 @@ #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "oat_file.h" #include "runtime_callbacks.h" diff --git a/runtime/Android.bp b/runtime/Android.bp index aba2b0e2a0..f2f7c3e3d0 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -14,12 +14,6 @@ // limitations under the License. // -// Keep the __jit_debug_register_code symbol as a unique symbol during ICF for architectures where -// we use gold as the linker (arm, x86, x86_64). The symbol is used by the debuggers to detect when -// new jit code is generated. We don't want it to be called when a different function with the same -// (empty) body is called. -JIT_DEBUG_REGISTER_CODE_LDFLAGS = ["-Wl,--keep-unique,__jit_debug_register_code"] - cc_defaults { name: "libdexfile_defaults", defaults: ["art_defaults"], @@ -33,6 +27,7 @@ cc_defaults { "dex/dex_file_tracking_registrar.cc", "dex/dex_file_verifier.cc", "dex/dex_instruction.cc", + "dex/modifiers.cc", "dex/standard_dex_file.cc", "utf.cc", "utils.cc", @@ -56,16 +51,8 @@ cc_defaults { ], }, }, - header_libs: [ - "jni_headers", - ], generated_sources: ["art_operator_srcs"], - // asm_support_gen.h (used by asm_support.h) is generated with cpp-define-generator - generated_headers: ["cpp-define-generator-asm-support"], - // export our headers so the libart-gtest targets can use it as well. - export_generated_headers: ["cpp-define-generator-asm-support"], include_dirs: [ - "external/icu/icu4c/source/common", "external/zlib", ], shared_libs: [ @@ -78,11 +65,20 @@ cc_defaults { // Exporting "." would shadow the system elf.h with our elf.h, // which in turn breaks any tools that reference this library. // export_include_dirs: ["."], +} - // ART's macros.h depends on libbase's macros.h. - // Note: runtime_options.h depends on cmdline. But we don't really want to export this - // generically. dex2oat takes care of it itself. - export_shared_lib_headers: ["libbase"], +gensrcs { + name: "dexfile_operator_srcs", + cmd: "$(location generate-operator-out.py) art/runtime $(in) > $(out)", + tool_files: ["generate-operator-out.py"], + srcs: [ + "dex/dex_file.h", + "dex/dex_file_layout.h", + "dex/dex_instruction.h", + "dex/dex_instruction_utils.h", + "dex/invoke_type.h", + ], + output_extension: "operator_out.cc", } art_cc_library { @@ -95,6 +91,12 @@ art_cc_library { }, } +// Keep the __jit_debug_register_code symbol as a unique symbol during ICF for architectures where +// we use gold as the linker (arm, x86, x86_64). The symbol is used by the debuggers to detect when +// new jit code is generated. We don't want it to be called when a different function with the same +// (empty) body is called. +JIT_DEBUG_REGISTER_CODE_LDFLAGS = ["-Wl,--keep-unique,__jit_debug_register_code"] + cc_defaults { name: "libart_defaults", defaults: ["art_defaults"], @@ -142,6 +144,7 @@ cc_defaults { "dex/dex_file_tracking_registrar.cc", "dex/dex_file_verifier.cc", "dex/dex_instruction.cc", + "dex/modifiers.cc", "dex/standard_dex_file.cc", "dex_to_dex_decompiler.cc", "elf_file.cc", @@ -535,6 +538,7 @@ gensrcs { "dex/dex_file_layout.h", "dex/dex_instruction.h", "dex/dex_instruction_utils.h", + "dex/invoke_type.h", "gc_root.h", "gc/allocator_type.h", "gc/allocator/rosalloc.h", @@ -547,7 +551,6 @@ gensrcs { "image.h", "instrumentation.h", "indirect_reference_table.h", - "invoke_type.h", "jdwp_provider.h", "jdwp/jdwp.h", "jdwp/jdwp_constants.h", diff --git a/runtime/art_field.h b/runtime/art_field.h index 46b013da7e..0eeeef2f2f 100644 --- a/runtime/art_field.h +++ b/runtime/art_field.h @@ -20,8 +20,8 @@ #include <jni.h> #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "offsets.h" #include "primitive.h" diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index c9a77331a7..65bacd8237 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -27,8 +27,9 @@ #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/invoke_type.h" #include "gc_root-inl.h" -#include "invoke_type.h" +#include "intrinsics_enum.h" #include "jit/profiling_info.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" @@ -412,7 +413,11 @@ inline void ArtMethod::SetIntrinsic(uint32_t intrinsic) { DCHECK_EQ(is_default_conflict, IsDefaultConflicting()); DCHECK_EQ(is_compilable, IsCompilable()); DCHECK_EQ(must_count_locks, MustCountLocks()); - DCHECK_EQ(hidden_api_list, GetHiddenApiAccessFlags()); + // We need to special case java.lang.ref.Reference.getRefererent. The Java method + // is hidden but we do not yet have a way of making intrinsics hidden. + if (intrinsic != static_cast<uint32_t>(Intrinsics::kReferenceGetReferent)) { + DCHECK_EQ(hidden_api_list, GetHiddenApiAccessFlags()); + } } else { SetAccessFlags(new_value); } diff --git a/runtime/art_method.h b/runtime/art_method.h index 4501450e05..ce8e8ac612 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -31,8 +31,8 @@ #include "dex/code_item_accessors.h" #include "dex/dex_file.h" #include "dex/dex_instruction_iterator.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "offsets.h" #include "primitive.h" diff --git a/runtime/base/file_utils.cc b/runtime/base/file_utils.cc index 63b4ac56d0..d22fd994ee 100644 --- a/runtime/base/file_utils.cc +++ b/runtime/base/file_utils.cc @@ -353,4 +353,9 @@ int MadviseLargestPageAlignedRegion(const uint8_t* begin, const uint8_t* end, in return 0; } +bool LocationIsOnSystem(const char* location) { + UniqueCPtr<const char[]> path(realpath(location, nullptr)); + return path != nullptr && android::base::StartsWith(path.get(), GetAndroidRoot().c_str()); +} + } // namespace art diff --git a/runtime/base/file_utils.h b/runtime/base/file_utils.h index e4555ad3cb..cac0950d9c 100644 --- a/runtime/base/file_utils.h +++ b/runtime/base/file_utils.h @@ -82,6 +82,9 @@ int64_t GetFileSizeBytes(const std::string& filename); // Madvise the largest page aligned region within begin and end. int MadviseLargestPageAlignedRegion(const uint8_t* begin, const uint8_t* end, int advice); +// Return whether the location is on system (i.e. android root). +bool LocationIsOnSystem(const char* location); + } // namespace art #endif // ART_RUNTIME_BASE_FILE_UTILS_H_ diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index 92d86519dc..03774f45cd 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -26,7 +26,7 @@ #include "class_linker-inl.h" #include "dex/dex_file-inl.h" #include "dex/dex_instruction-inl.h" -#include "invoke_type.h" +#include "dex/invoke_type.h" #include "mirror/class-inl.h" #include "mirror/method_type.h" #include "mirror/object-inl.h" diff --git a/runtime/dex/dex_file.h b/runtime/dex/dex_file.h index 1ee48f71bc..511ce312cd 100644 --- a/runtime/dex/dex_file.h +++ b/runtime/dex/dex_file.h @@ -995,6 +995,10 @@ class DexFile { return Begin() <= addr && addr < Begin() + Size(); } + DexFileContainer* GetContainer() const { + return container_.get(); + } + protected: // First Dex format version supporting default methods. static const uint32_t kDefaultMethodsVersion = 37; diff --git a/runtime/dex/dex_file_verifier.cc b/runtime/dex/dex_file_verifier.cc index 7265aad1ba..f7fdbb027c 100644 --- a/runtime/dex/dex_file_verifier.cc +++ b/runtime/dex/dex_file_verifier.cc @@ -27,6 +27,7 @@ #include "dex_file-inl.h" #include "experimental_flags.h" #include "leb128.h" +#include "modifiers.h" #include "safe_map.h" #include "utf-inl.h" #include "utils.h" @@ -453,6 +454,7 @@ bool DexFileVerifier::CheckMap() { uint32_t count = map->size_; uint32_t last_offset = 0; + uint32_t last_type = 0; uint32_t data_item_count = 0; uint32_t data_items_left = header_->data_size_; uint32_t used_bits = 0; @@ -465,7 +467,11 @@ bool DexFileVerifier::CheckMap() { // Check the items listed in the map. for (uint32_t i = 0; i < count; i++) { if (UNLIKELY(last_offset >= item->offset_ && i != 0)) { - ErrorStringPrintf("Out of order map item: %x then %x", last_offset, item->offset_); + ErrorStringPrintf("Out of order map item: %x then %x for type %x last type was %x", + last_offset, + item->offset_, + static_cast<uint32_t>(item->type_), + last_type); return false; } if (UNLIKELY(item->offset_ >= header_->file_size_)) { @@ -501,6 +507,7 @@ bool DexFileVerifier::CheckMap() { used_bits |= bit; last_offset = item->offset_; + last_type = item->type_; item++; } diff --git a/runtime/invoke_type.h b/runtime/dex/invoke_type.h index 2b877e6f51..726d269a3e 100644 --- a/runtime/invoke_type.h +++ b/runtime/dex/invoke_type.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_INVOKE_TYPE_H_ -#define ART_RUNTIME_INVOKE_TYPE_H_ +#ifndef ART_RUNTIME_DEX_INVOKE_TYPE_H_ +#define ART_RUNTIME_DEX_INVOKE_TYPE_H_ #include <iosfwd> @@ -35,4 +35,4 @@ std::ostream& operator<<(std::ostream& os, const InvokeType& rhs); } // namespace art -#endif // ART_RUNTIME_INVOKE_TYPE_H_ +#endif // ART_RUNTIME_DEX_INVOKE_TYPE_H_ diff --git a/runtime/dex/modifiers.cc b/runtime/dex/modifiers.cc new file mode 100644 index 0000000000..30daefb172 --- /dev/null +++ b/runtime/dex/modifiers.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +#include "modifiers.h" + +namespace art { + +std::string PrettyJavaAccessFlags(uint32_t access_flags) { + std::string result; + if ((access_flags & kAccPublic) != 0) { + result += "public "; + } + if ((access_flags & kAccProtected) != 0) { + result += "protected "; + } + if ((access_flags & kAccPrivate) != 0) { + result += "private "; + } + if ((access_flags & kAccFinal) != 0) { + result += "final "; + } + if ((access_flags & kAccStatic) != 0) { + result += "static "; + } + if ((access_flags & kAccAbstract) != 0) { + result += "abstract "; + } + if ((access_flags & kAccInterface) != 0) { + result += "interface "; + } + if ((access_flags & kAccTransient) != 0) { + result += "transient "; + } + if ((access_flags & kAccVolatile) != 0) { + result += "volatile "; + } + if ((access_flags & kAccSynchronized) != 0) { + result += "synchronized "; + } + return result; +} + +} // namespace art diff --git a/runtime/modifiers.h b/runtime/dex/modifiers.h index 0e2db932bb..2998f602d4 100644 --- a/runtime/modifiers.h +++ b/runtime/dex/modifiers.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_MODIFIERS_H_ -#define ART_RUNTIME_MODIFIERS_H_ +#ifndef ART_RUNTIME_DEX_MODIFIERS_H_ +#define ART_RUNTIME_DEX_MODIFIERS_H_ #include <stdint.h> @@ -138,7 +138,11 @@ static constexpr uint32_t kAccValidInterfaceFlags = kAccPublic | kAccInterface | static constexpr uint32_t kAccVisibilityFlags = kAccPublic | kAccPrivate | kAccProtected; +// Returns a human-readable version of the Java part of the access flags, e.g., "private static " +// (note the trailing whitespace). +std::string PrettyJavaAccessFlags(uint32_t access_flags); + } // namespace art -#endif // ART_RUNTIME_MODIFIERS_H_ +#endif // ART_RUNTIME_DEX_MODIFIERS_H_ diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 3048f45f30..9ef7d426df 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -25,12 +25,12 @@ #include "class_linker-inl.h" #include "common_throws.h" #include "dex/dex_file.h" +#include "dex/invoke_type.h" #include "entrypoints/quick/callee_save_frame.h" #include "handle_scope-inl.h" #include "imt_conflict_table.h" #include "imtable-inl.h" #include "indirect_reference_table.h" -#include "invoke_type.h" #include "jni_internal.h" #include "mirror/array.h" #include "mirror/class-inl.h" diff --git a/runtime/entrypoints/quick/quick_throw_entrypoints.cc b/runtime/entrypoints/quick/quick_throw_entrypoints.cc index 4b26beece3..db4891ebd8 100644 --- a/runtime/entrypoints/quick/quick_throw_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_throw_entrypoints.cc @@ -126,6 +126,14 @@ extern "C" NO_RETURN void artThrowClassCastException(mirror::Class* dest_type, dex::TypeIndex type_index(check_cast.VRegB_21c()); ClassLinker* linker = Runtime::Current()->GetClassLinker(); dest_type = linker->LookupResolvedType(type_index, visitor.caller).Ptr(); + if (UNLIKELY(dest_type == nullptr)) { + // This class must have been resolved to the boot image at AOT compile time + // but it's not yet resolved in the app's class loader. Just look it up in + // the boot class path loader. + DCHECK(visitor.caller->GetClassLoader() != nullptr); + dest_type = linker->LookupResolvedType( + type_index, visitor.caller->GetDexCache(), /* class_loader */ nullptr).Ptr(); + } CHECK(dest_type != nullptr) << "Target class should have been previously resolved: " << visitor.caller->GetDexFile()->PrettyType(type_index); } diff --git a/runtime/hidden_api_access_flags.h b/runtime/hidden_api_access_flags.h index 80a002d96e..c328f965d2 100644 --- a/runtime/hidden_api_access_flags.h +++ b/runtime/hidden_api_access_flags.h @@ -18,7 +18,7 @@ #define ART_RUNTIME_HIDDEN_API_ACCESS_FLAGS_H_ #include "base/bit_utils.h" -#include "modifiers.h" +#include "dex/modifiers.h" namespace art { diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index bb5bc887f1..b4265d1a28 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -287,16 +287,15 @@ class ProfileCompilationInfoTest : public CommonRuntimeTest { // Prepare the profile content for zipping. ASSERT_TRUE(profile.GetFile()->ResetOffset()); - uint64_t data_size = profile.GetFile()->GetLength(); - std::unique_ptr<uint8_t> data(new uint8_t[data_size]); - ASSERT_TRUE(profile.GetFile()->ReadFully(data.get(), data_size)); + std::vector<uint8_t> data(profile.GetFile()->GetLength()); + ASSERT_TRUE(profile.GetFile()->ReadFully(data.data(), data.size())); // Zip the profile content. ScratchFile zip; FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb"); ZipWriter writer(file); writer.StartEntry(zip_entry, zip_flags); - writer.WriteBytes(data.get(), data_size); + writer.WriteBytes(data.data(), data.size()); writer.FinishEntry(); writer.Finish(); fflush(file); @@ -1020,16 +1019,15 @@ TEST_F(ProfileCompilationInfoTest, LoadFromZipFailBadProfile) { // Prepare the profile content for zipping. ASSERT_TRUE(profile.GetFile()->ResetOffset()); - uint64_t data_size = profile.GetFile()->GetLength(); - std::unique_ptr<uint8_t> data(new uint8_t[data_size]); - ASSERT_TRUE(profile.GetFile()->ReadFully(data.get(), data_size)); + std::vector<uint8_t> data(profile.GetFile()->GetLength()); + ASSERT_TRUE(profile.GetFile()->ReadFully(data.data(), data.size())); // Zip the profile content. ScratchFile zip; FILE* file = fopen(zip.GetFile()->GetPath().c_str(), "wb"); ZipWriter writer(file); - writer.StartEntry("primary.prof", ZipWriter::kCompress | ZipWriter::kAlign32); - writer.WriteBytes(data.get(), data_size); + writer.StartEntry("primary.prof", ZipWriter::kAlign32); + writer.WriteBytes(data.data(), data.size()); writer.FinishEntry(); writer.Finish(); fflush(file); diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 302a5e622e..cd313b32ab 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -26,11 +26,11 @@ #include "class_linker.h" #include "class_loader.h" #include "common_throws.h" -#include "dex_cache.h" #include "dex/dex_file-inl.h" +#include "dex/invoke_type.h" +#include "dex_cache.h" #include "gc/heap-inl.h" #include "iftable.h" -#include "invoke_type.h" #include "subtype_check.h" #include "object-inl.h" #include "object_array.h" diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 84b032620f..ced7c7c908 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -25,10 +25,10 @@ #include "class_status.h" #include "dex/dex_file.h" #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "gc/allocator_type.h" #include "gc_root.h" #include "imtable.h" -#include "modifiers.h" #include "object.h" #include "object_array.h" #include "primitive.h" diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h index 6845575d18..dd09be331a 100644 --- a/runtime/mirror/field.h +++ b/runtime/mirror/field.h @@ -19,8 +19,8 @@ #include "accessible_object.h" #include "base/enums.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "object.h" #include "primitive.h" diff --git a/runtime/mirror/method_handles_lookup.cc b/runtime/mirror/method_handles_lookup.cc index a390a2ef53..039bbf2932 100644 --- a/runtime/mirror/method_handles_lookup.cc +++ b/runtime/mirror/method_handles_lookup.cc @@ -17,11 +17,11 @@ #include "method_handles_lookup.h" #include "class-inl.h" +#include "dex/modifiers.h" #include "gc_root-inl.h" #include "handle_scope.h" #include "jni_internal.h" #include "mirror/method_handle_impl.h" -#include "modifiers.h" #include "object-inl.h" #include "well_known_classes.h" diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index c03dbccbc4..9fd99057a1 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -620,14 +620,6 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { dex_file_location.c_str()); return false; } - if (UNLIKELY(dex_file_offset == 0U)) { - *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with zero dex " - "file offset", - GetLocation().c_str(), - i, - dex_file_location.c_str()); - return false; - } if (UNLIKELY(dex_file_offset > DexSize())) { *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file " "offset %u > %zu", @@ -638,20 +630,45 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { DexSize()); return false; } - if (UNLIKELY(DexSize() - dex_file_offset < sizeof(DexFile::Header))) { - *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file " - "offset %u of %zu but the size of dex file header is %zu", - GetLocation().c_str(), - i, - dex_file_location.c_str(), - dex_file_offset, - DexSize(), - sizeof(DexFile::Header)); - return false; + const uint8_t* dex_file_pointer = nullptr; + if (UNLIKELY(dex_file_offset == 0U)) { + if (uncompressed_dex_files_ == nullptr) { + uncompressed_dex_files_.reset(new std::vector<std::unique_ptr<const DexFile>>()); + // No dex files, load it from location. + const ArtDexFileLoader dex_file_loader; + if (!dex_file_loader.Open(dex_file_location.c_str(), + dex_file_location, + /* verify */ false, + /* verify_checksum */ false, + error_msg, + uncompressed_dex_files_.get())) { + if (Runtime::Current() == nullptr) { + // If there's no runtime, we're running oatdump, so return + // a half constructed oat file that oatdump knows how to deal with. + LOG(WARNING) << "Could not find associated dex files of oat file. " + << "Oatdump will only dump the header."; + return true; + } else { + return false; + } + } + } + dex_file_pointer = uncompressed_dex_files_.get()->at(i)->Begin(); + } else { + if (UNLIKELY(DexSize() - dex_file_offset < sizeof(DexFile::Header))) { + *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file " + "offset %u of %zu but the size of dex file header is %zu", + GetLocation().c_str(), + i, + dex_file_location.c_str(), + dex_file_offset, + DexSize(), + sizeof(DexFile::Header)); + return false; + } + dex_file_pointer = DexBegin() + dex_file_offset; } - const uint8_t* dex_file_pointer = DexBegin() + dex_file_offset; - const bool valid_magic = DexFileLoader::IsMagicValid(dex_file_pointer); if (UNLIKELY(!valid_magic)) { *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with invalid " @@ -672,7 +689,7 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { return false; } const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_pointer); - if (DexSize() - dex_file_offset < header->file_size_) { + if (dex_file_offset != 0 && (DexSize() - dex_file_offset < header->file_size_)) { *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file " "offset %u and size %u truncated at %zu", GetLocation().c_str(), @@ -1297,13 +1314,15 @@ bool ElfOatFile::ElfFileOpen(File* file, std::string OatFile::ResolveRelativeEncodedDexLocation( const char* abs_dex_location, const std::string& rel_dex_location) { - if (abs_dex_location != nullptr && rel_dex_location[0] != '/') { + // For host, we still do resolution as the rel_dex_location might be absolute + // for a target dex (for example /system/foo/foo.apk). + if (abs_dex_location != nullptr && (rel_dex_location[0] != '/' || !kIsTargetBuild)) { // Strip :classes<N>.dex used for secondary multidex files. std::string base = DexFileLoader::GetBaseLocation(rel_dex_location); std::string multidex_suffix = DexFileLoader::GetMultiDexSuffix(rel_dex_location); // Check if the base is a suffix of the provided abs_dex_location. - std::string target_suffix = "/" + base; + std::string target_suffix = ((rel_dex_location[0] != '/') ? "/" : "") + base; std::string abs_location(abs_dex_location); if (abs_location.size() > target_suffix.size()) { size_t pos = abs_location.size() - target_suffix.size(); diff --git a/runtime/oat_file.h b/runtime/oat_file.h index bf22e0b88b..50a706d1ba 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -328,6 +328,11 @@ class OatFile { return vdex_.get(); } + // Whether the OatFile embeds the Dex code. + bool ContainsDexCode() const { + return uncompressed_dex_files_ == nullptr; + } + protected: OatFile(const std::string& filename, bool executable); @@ -399,6 +404,10 @@ class OatFile { // elements. std::list<> and std::deque<> satisfy this requirement, std::vector<> doesn't. mutable std::list<std::string> string_cache_ GUARDED_BY(secondary_lookup_lock_); + // Cache of dex files mapped directly from a location, in case the OatFile does + // not embed the dex code. + std::unique_ptr<std::vector<std::unique_ptr<const DexFile>>> uncompressed_dex_files_; + friend class gc::collector::DummyOatFile; // For modifying begin_ and end_. friend class OatClass; friend class art::OatDexFile; diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 73ca19a363..15a5954396 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -72,9 +72,12 @@ std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStat OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable) + bool load_executable, + bool only_load_system_executable) : OatFileAssistant(dex_location, - isa, load_executable, + isa, + load_executable, + only_load_system_executable, -1 /* vdex_fd */, -1 /* oat_fd */, -1 /* zip_fd */) {} @@ -83,11 +86,13 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, bool load_executable, + bool only_load_system_executable, int vdex_fd, int oat_fd, int zip_fd) : isa_(isa), load_executable_(load_executable), + only_load_system_executable_(only_load_system_executable), odex_(this, /*is_oat_location*/ false), oat_(this, /*is_oat_location*/ true), zip_fd_(zip_fd) { @@ -1122,6 +1127,10 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { if (!load_attempted_) { load_attempted_ = true; if (filename_provided_) { + bool executable = oat_file_assistant_->load_executable_; + if (executable && oat_file_assistant_->only_load_system_executable_) { + executable = LocationIsOnSystem(filename_.c_str()); + } std::string error_msg; if (use_fd_) { if (oat_fd_ >= 0 && vdex_fd_ >= 0) { @@ -1130,7 +1139,7 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { filename_.c_str(), nullptr, nullptr, - oat_file_assistant_->load_executable_, + executable, false /* low_4gb */, oat_file_assistant_->dex_location_.c_str(), &error_msg)); @@ -1140,7 +1149,7 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { filename_.c_str(), nullptr, nullptr, - oat_file_assistant_->load_executable_, + executable, false /* low_4gb */, oat_file_assistant_->dex_location_.c_str(), &error_msg)); diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 6c01c1e880..a6140304c2 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -119,9 +119,13 @@ class OatFileAssistant { // // load_executable should be true if the caller intends to try and load // executable code for this dex location. + // + // only_load_system_executable should be true if the caller intends to have + // only oat files from /system loaded executable. OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable); + bool load_executable, + bool only_load_system_executable = false); // Similar to this(const char*, const InstructionSet, bool), however, if a valid zip_fd is // provided, vdex, oat, and zip files will be read from vdex_fd, oat_fd and zip_fd respectively. @@ -129,6 +133,7 @@ class OatFileAssistant { OatFileAssistant(const char* dex_location, const InstructionSet isa, bool load_executable, + bool only_load_system_executable, int vdex_fd, int oat_fd, int zip_fd); @@ -487,6 +492,9 @@ class OatFileAssistant { // Whether we will attempt to load oat files executable. bool load_executable_ = false; + // Whether only oat files on /system are loaded executable. + const bool only_load_system_executable_ = false; + // Cached value of the required dex checksums. // This should be accessed only by the GetRequiredDexChecksums() method. std::vector<uint32_t> cached_required_dex_checksums_; diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index a98da0f029..50f5e7a0d5 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -246,6 +246,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, vdex_fd.get(), odex_fd.get(), zip_fd.get()); @@ -285,6 +286,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, vdex_fd.get(), -1 /* oat_fd */, zip_fd.get()); @@ -319,6 +321,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, -1 /* vdex_fd */, odex_fd.get(), zip_fd.get()); @@ -342,6 +345,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, -1 /* vdex_fd */, -1 /* oat_fd */, zip_fd); @@ -1439,6 +1443,60 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { default_filter, false, false, relative_context.get())); } +TEST_F(OatFileAssistantTest, SystemOdex) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; + std::string system_location = GetAndroidRoot() + "/OatUpToDate.jar"; + + std::string error_msg; + + Copy(GetDexSrc1(), dex_location); + EXPECT_FALSE(LocationIsOnSystem(dex_location.c_str())); + + { + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + true, + false); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + { + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + true, + true); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_FALSE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + Copy(GetDexSrc1(), system_location); + EXPECT_TRUE(LocationIsOnSystem(system_location.c_str())); + + { + OatFileAssistant oat_file_assistant(system_location.c_str(), + kRuntimeISA, + true, + false); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + { + OatFileAssistant oat_file_assistant(system_location.c_str(), + kRuntimeISA, + true, + true); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } +} + // TODO: More Tests: // * Test class linker falls back to unquickened dex for DexNoOat // * Test class linker falls back to unquickened dex for MultiDexNoOat diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 9503360167..e4194442d3 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -56,15 +56,11 @@ using android::base::StringPrintf; // If true, we attempt to load the application image if it exists. static constexpr bool kEnableAppImage = true; -static bool OatFileIsOnSystem(const std::unique_ptr<const OatFile>& oat_file) { - UniqueCPtr<const char[]> path(realpath(oat_file->GetLocation().c_str(), nullptr)); - return path != nullptr && android::base::StartsWith(oat_file->GetLocation(), - GetAndroidRoot().c_str()); -} - const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file) { WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); - CHECK(!only_use_system_oat_files_ || OatFileIsOnSystem(oat_file)) + CHECK(!only_use_system_oat_files_ || + LocationIsOnSystem(oat_file->GetLocation().c_str()) || + !oat_file->IsExecutable()) << "Registering a non /system oat file: " << oat_file->GetLocation(); DCHECK(oat_file != nullptr); if (kIsDebugBuild) { @@ -424,7 +420,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( OatFileAssistant oat_file_assistant(dex_location, kRuntimeISA, - !runtime->IsAotCompiler()); + !runtime->IsAotCompiler(), + only_use_system_oat_files_); // Lock the target oat location to avoid races generating and loading the // oat file. @@ -437,8 +434,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const OatFile* source_oat_file = nullptr; - // No point in trying to make up-to-date if we can only use system oat files. - if (!only_use_system_oat_files_ && !oat_file_assistant.IsUpToDate()) { + 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. @@ -474,9 +470,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); - if (oat_file != nullptr && only_use_system_oat_files_ && !OatFileIsOnSystem(oat_file)) { - // If the oat file is not on /system, don't use it. - } else if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { + if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it // could load oat files without checking the classpath, which would be incorrect. diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h index dd6b7ba2ff..038474e31f 100644 --- a/runtime/oat_file_manager.h +++ b/runtime/oat_file_manager.h @@ -127,6 +127,9 @@ class OatFileManager { std::set<std::unique_ptr<const OatFile>> oat_files_ GUARDED_BY(Locks::oat_file_manager_lock_); bool have_non_pic_oat_file_; + + // Only use the compiled code in an OAT file when the file is on /system. If the OAT file + // is not on /system, don't load it "executable". bool only_use_system_oat_files_; DISALLOW_COPY_AND_ASSIGN(OatFileManager); diff --git a/runtime/runtime_intrinsics.cc b/runtime/runtime_intrinsics.cc index f710ebeb4c..3295a86e59 100644 --- a/runtime/runtime_intrinsics.cc +++ b/runtime/runtime_intrinsics.cc @@ -18,8 +18,8 @@ #include "art_method-inl.h" #include "class_linker.h" +#include "dex/invoke_type.h" #include "intrinsics_enum.h" -#include "invoke_type.h" #include "mirror/class.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" diff --git a/runtime/utf.cc b/runtime/utf.cc index 93fcb32136..32ae187297 100644 --- a/runtime/utf.cc +++ b/runtime/utf.cc @@ -18,8 +18,7 @@ #include <android-base/logging.h> -#include "mirror/array.h" -#include "mirror/object-inl.h" +#include "base/casts.h" #include "utf-inl.h" namespace art { diff --git a/runtime/utils.cc b/runtime/utils.cc index 79ddcb9bff..b2ec669f32 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -30,7 +30,6 @@ #include "android-base/stringprintf.h" #include "android-base/strings.h" -#include "dex/dex_file-inl.h" #include "os.h" #include "utf-inl.h" @@ -126,41 +125,6 @@ std::string PrettyDescriptor(const char* descriptor) { return result; } -std::string PrettyJavaAccessFlags(uint32_t access_flags) { - std::string result; - if ((access_flags & kAccPublic) != 0) { - result += "public "; - } - if ((access_flags & kAccProtected) != 0) { - result += "protected "; - } - if ((access_flags & kAccPrivate) != 0) { - result += "private "; - } - if ((access_flags & kAccFinal) != 0) { - result += "final "; - } - if ((access_flags & kAccStatic) != 0) { - result += "static "; - } - if ((access_flags & kAccAbstract) != 0) { - result += "abstract "; - } - if ((access_flags & kAccInterface) != 0) { - result += "interface "; - } - if ((access_flags & kAccTransient) != 0) { - result += "transient "; - } - if ((access_flags & kAccVolatile) != 0) { - result += "volatile "; - } - if ((access_flags & kAccSynchronized) != 0) { - result += "synchronized "; - } - return result; -} - std::string PrettySize(int64_t byte_count) { // The byte thresholds at which we display amounts. A byte count is displayed // in unit U when kUnitThresholds[U] <= bytes < kUnitThresholds[U+1]. diff --git a/runtime/utils.h b/runtime/utils.h index 7402c12280..abdafcc9f7 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -82,10 +82,6 @@ void AppendPrettyDescriptor(const char* descriptor, std::string* result); std::string PrettyDescriptor(const char* descriptor); std::string PrettyDescriptor(Primitive::Type type); -// Returns a human-readable version of the Java part of the access flags, e.g., "private static " -// (note the trailing whitespace). -std::string PrettyJavaAccessFlags(uint32_t access_flags); - // Returns a human-readable size string such as "1MB". std::string PrettySize(int64_t size_in_bytes); diff --git a/runtime/zip_archive.cc b/runtime/zip_archive.cc index f3d4d77214..2caed4b391 100644 --- a/runtime/zip_archive.cc +++ b/runtime/zip_archive.cc @@ -29,6 +29,7 @@ #include "base/bit_utils.h" #include "base/unix_file/fd_file.h" +#include "dex/dex_file.h" namespace art { @@ -49,11 +50,15 @@ bool ZipEntry::IsUncompressed() { return zip_entry_->method == kCompressStored; } -bool ZipEntry::IsAlignedTo(size_t alignment) { +bool ZipEntry::IsAlignedTo(size_t alignment) const { DCHECK(IsPowerOfTwo(alignment)) << alignment; return IsAlignedParam(zip_entry_->offset, static_cast<int>(alignment)); } +bool ZipEntry::IsAlignedToDexHeader() const { + return IsAlignedTo(alignof(DexFile::Header)); +} + ZipEntry::~ZipEntry() { delete zip_entry_; } diff --git a/runtime/zip_archive.h b/runtime/zip_archive.h index 75f8757f6c..70518e1360 100644 --- a/runtime/zip_archive.h +++ b/runtime/zip_archive.h @@ -59,7 +59,8 @@ class ZipEntry { uint32_t GetCrc32(); bool IsUncompressed(); - bool IsAlignedTo(size_t alignment); + bool IsAlignedTo(size_t alignment) const; + bool IsAlignedToDexHeader() const; private: ZipEntry(ZipArchiveHandle handle, diff --git a/test/552-checker-sharpening/src/Main.java b/test/552-checker-sharpening/src/Main.java index 3173afdfcd..0623a22d64 100644 --- a/test/552-checker-sharpening/src/Main.java +++ b/test/552-checker-sharpening/src/Main.java @@ -14,8 +14,17 @@ * limitations under the License. */ +import java.io.ByteArrayInputStream; +import java.io.InputStream; + public class Main { + public static void assertBooleanEquals(boolean expected, boolean result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + public static void assertIntEquals(int expected, int result) { if (expected != result) { throw new Error("Expected: " + expected + ", found: " + result); @@ -195,6 +204,14 @@ public class Main { return Other.class; } + /// CHECK-START: boolean Main.$noinline$instanceOfInputStream(java.lang.Object) builder (after) + /// CHECK-NOT: LoadClass + /// CHECK: InstanceOf check_kind:bitstring_check + public static boolean $noinline$instanceOfInputStream(Object o) { + // InputStream is known to be in the core image with an initialized type check bitstring. + return o instanceof InputStream; + } + public static void main(String[] args) { assertIntEquals(1, testSimple(1)); assertIntEquals(1, testDiamond(false, 1)); @@ -208,6 +225,10 @@ public class Main { assertStringEquals("non-boot-image-string", $noinline$getNonBootImageString()); assertClassEquals(String.class, $noinline$getStringClass()); assertClassEquals(Other.class, $noinline$getOtherClass()); + assertBooleanEquals(false, $noinline$instanceOfInputStream(null)); + assertBooleanEquals(false, $noinline$instanceOfInputStream(new Integer(1))); + assertBooleanEquals(true, + $noinline$instanceOfInputStream(new ByteArrayInputStream(new byte[10]))); } } diff --git a/test/674-hiddenapi/api-blacklist.txt b/test/674-hiddenapi/api-blacklist.txt new file mode 100644 index 0000000000..d43360c62f --- /dev/null +++ b/test/674-hiddenapi/api-blacklist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorBlacklist;-><init>()V +LParentClass;->fieldPublicBlacklist:I +LParentClass;->fieldPackageBlacklist:I +LParentClass;->fieldProtectedBlacklist:I +LParentClass;->fieldPrivateBlacklist:I +LParentClass;->fieldPublicStaticBlacklist:I +LParentClass;->fieldPackageStaticBlacklist:I +LParentClass;->fieldProtectedStaticBlacklist:I +LParentClass;->fieldPrivateStaticBlacklist:I +LParentClass;->methodPublicBlacklist()I +LParentClass;->methodPackageBlacklist()I +LParentClass;->methodProtectedBlacklist()I +LParentClass;->methodPrivateBlacklist()I +LParentClass;->methodPublicStaticBlacklist()I +LParentClass;->methodPackageStaticBlacklist()I +LParentClass;->methodProtectedStaticBlacklist()I +LParentClass;->methodPrivateStaticBlacklist()I +LParentClass;-><init>(IC)V +LParentClass;-><init>(FC)V +LParentClass;-><init>(JC)V +LParentClass;-><init>(DC)V +LParentInterface;->fieldPublicStaticBlacklist:I +LParentInterface;->methodPublicBlacklist()I +LParentInterface;->methodPublicStaticBlacklist()I +LParentInterface;->methodPublicDefaultBlacklist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/api-dark-greylist.txt b/test/674-hiddenapi/api-dark-greylist.txt new file mode 100644 index 0000000000..d0f35f64bc --- /dev/null +++ b/test/674-hiddenapi/api-dark-greylist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorDarkGreylist;-><init>()V +LParentClass;->fieldPublicDarkGreylist:I +LParentClass;->fieldPackageDarkGreylist:I +LParentClass;->fieldProtectedDarkGreylist:I +LParentClass;->fieldPrivateDarkGreylist:I +LParentClass;->fieldPublicStaticDarkGreylist:I +LParentClass;->fieldPackageStaticDarkGreylist:I +LParentClass;->fieldProtectedStaticDarkGreylist:I +LParentClass;->fieldPrivateStaticDarkGreylist:I +LParentClass;->methodPublicDarkGreylist()I +LParentClass;->methodPackageDarkGreylist()I +LParentClass;->methodProtectedDarkGreylist()I +LParentClass;->methodPrivateDarkGreylist()I +LParentClass;->methodPublicStaticDarkGreylist()I +LParentClass;->methodPackageStaticDarkGreylist()I +LParentClass;->methodProtectedStaticDarkGreylist()I +LParentClass;->methodPrivateStaticDarkGreylist()I +LParentClass;-><init>(IB)V +LParentClass;-><init>(FB)V +LParentClass;-><init>(JB)V +LParentClass;-><init>(DB)V +LParentInterface;->fieldPublicStaticDarkGreylist:I +LParentInterface;->methodPublicDarkGreylist()I +LParentInterface;->methodPublicStaticDarkGreylist()I +LParentInterface;->methodPublicDefaultDarkGreylist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/api-light-greylist.txt b/test/674-hiddenapi/api-light-greylist.txt new file mode 100644 index 0000000000..2809025cfd --- /dev/null +++ b/test/674-hiddenapi/api-light-greylist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorLightGreylist;-><init>()V +LParentClass;->fieldPublicLightGreylist:I +LParentClass;->fieldPackageLightGreylist:I +LParentClass;->fieldProtectedLightGreylist:I +LParentClass;->fieldPrivateLightGreylist:I +LParentClass;->fieldPublicStaticLightGreylist:I +LParentClass;->fieldPackageStaticLightGreylist:I +LParentClass;->fieldProtectedStaticLightGreylist:I +LParentClass;->fieldPrivateStaticLightGreylist:I +LParentClass;->methodPublicLightGreylist()I +LParentClass;->methodPackageLightGreylist()I +LParentClass;->methodProtectedLightGreylist()I +LParentClass;->methodPrivateLightGreylist()I +LParentClass;->methodPublicStaticLightGreylist()I +LParentClass;->methodPackageStaticLightGreylist()I +LParentClass;->methodProtectedStaticLightGreylist()I +LParentClass;->methodPrivateStaticLightGreylist()I +LParentClass;-><init>(IZ)V +LParentClass;-><init>(FZ)V +LParentClass;-><init>(JZ)V +LParentClass;-><init>(DZ)V +LParentInterface;->fieldPublicStaticLightGreylist:I +LParentInterface;->methodPublicLightGreylist()I +LParentInterface;->methodPublicStaticLightGreylist()I +LParentInterface;->methodPublicDefaultLightGreylist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build new file mode 100644 index 0000000000..330a6def29 --- /dev/null +++ b/test/674-hiddenapi/build @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Copyright 2018 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. + +set -e + +# Build the jars twice. First with applying hiddenapi, creating a boot jar, then +# a second time without to create a normal jar. We need to do this because we +# want to load the jar once as an app module and once as a member of the boot +# class path. The DexFileVerifier would fail on the former as it does not allow +# hidden API access flags in dex files. DexFileVerifier is not invoked on boot +# class path dex files, so the boot jar loads fine in the latter case. + +export USE_HIDDENAPI=true +./default-build "$@" + +# Move the jar file into the resource folder to be bundled with the test. +mkdir res +mv ${TEST_NAME}.jar res/boot.jar + +# Clear all intermediate files otherwise default-build would either skip +# compilation or fail rebuilding. +rm -rf classes* + +export USE_HIDDENAPI=false +./default-build "$@" diff --git a/test/674-hiddenapi/check b/test/674-hiddenapi/check new file mode 100644 index 0000000000..c319a0ae97 --- /dev/null +++ b/test/674-hiddenapi/check @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. + +# Remove pid and date from the log messages. +grep -vE '^dalvikvm(32|64) E [^]]+]' "$2" \ + | grep -v JNI_OnLoad \ + | grep -v JNI_OnUnload \ + > "$2.tmp" + +./default-check "$1" "$2.tmp" diff --git a/test/674-hiddenapi/expected.txt b/test/674-hiddenapi/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/674-hiddenapi/expected.txt diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..672079b9d8 --- /dev/null +++ b/test/674-hiddenapi/hiddenapi.cc @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "class_linker.h" +#include "dex/art_dex_file_loader.h" +#include "jni.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread.h" +#include "ti-agent/scoped_utf_chars.h" + +namespace art { +namespace Test674HiddenApi { + +extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader( + JNIEnv* env, jclass, jstring jpath) { + ScopedUtfChars utf(env, jpath); + const char* path = utf.c_str(); + if (path == nullptr) { + return; + } + + ArtDexFileLoader dex_loader; + std::string error_msg; + std::vector<std::unique_ptr<const DexFile>> dex_files; + if (!dex_loader.Open(path, + path, + /* verify */ false, + /* verify_checksum */ true, + &error_msg, + &dex_files)) { + LOG(FATAL) << "Could not open " << path << " for boot classpath extension: " << error_msg; + UNREACHABLE(); + } + + ScopedObjectAccess soa(Thread::Current()); + for (std::unique_ptr<const DexFile>& dex_file : dex_files) { + Runtime::Current()->GetClassLinker()->AppendToBootClassPath( + Thread::Current(), *dex_file.release()); + } +} + +static jobject NewInstance(JNIEnv* env, jclass klass) { + jmethodID constructor = env->GetMethodID(klass, "<init>", "()V"); + if (constructor == NULL) { + return NULL; + } + return env->NewObject(klass, constructor); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canGetField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + if (is_static) { + env->GetStaticIntField(klass, field); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->GetIntField(obj, field); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canSetField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + if (is_static) { + env->SetStaticIntField(klass, field, 42); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->SetIntField(obj, field, 42); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverMethod( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodA( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + if (is_static) { + env->CallStaticIntMethodA(klass, method, nullptr); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->CallIntMethodA(obj, method, nullptr); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodV( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + if (is_static) { + env->CallStaticIntMethod(klass, method); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->CallIntMethod(obj, method); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +static constexpr size_t kConstructorSignatureLength = 5; // e.g. (IZ)V +static constexpr size_t kNumConstructorArgs = kConstructorSignatureLength - 3; + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverConstructor( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorA( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + // CheckJNI won't allow out-of-range values, so just zero everything. + CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength); + size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs; + jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size)); + memset(initargs, 0, initargs_size); + + env->NewObjectA(klass, constructor, initargs); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorV( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + // CheckJNI won't allow out-of-range values, so just zero everything. + CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength); + size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs; + jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size)); + memset(initargs, 0, initargs_size); + + static_assert(kNumConstructorArgs == 2, "Change the varargs below if you change the constant"); + env->NewObject(klass, constructor, initargs[0], initargs[1]); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jint JNICALL Java_Reflection_getHiddenApiAccessFlags(JNIEnv*, jclass) { + return static_cast<jint>(kAccHiddenApiBits); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_ChildClass_hasPendingWarning(JNIEnv*, jclass) { + return false; +} + +extern "C" JNIEXPORT void JNICALL Java_ChildClass_clearWarning(JNIEnv*, jclass) { + return; +} + +} // namespace Test674HiddenApi +} // namespace art diff --git a/test/674-hiddenapi/info.txt b/test/674-hiddenapi/info.txt new file mode 100644 index 0000000000..25ac6ae78f --- /dev/null +++ b/test/674-hiddenapi/info.txt @@ -0,0 +1,15 @@ +Test whether hidden API access flags are being enforced. The test is composed of +two JARs. The first (parent) defines methods and fields and the second (child) +tries to access them with reflection/JNI or link against them. Note that the +first is compiled twice - once with and once without hidden access flags. + +The test then proceeds to exercise the following combinations of class loading: +(a) Both parent and child dex loaded with PathClassLoader, parent's class loader + is the child's class loader's parent. Access flags should not be enforced as + the parent does not belong to boot class path. +(b) Parent is appended to boot class path, child is loaded with PathClassLoader. + In this situation child should not be able to access hidden methods/fields + of the parent. +(c) Both parent and child are appended to boot class path. Restrictions should + not apply as hidden APIs are accessible within the boundaries of the boot + class path. diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java new file mode 100644 index 0000000000..56a644f637 --- /dev/null +++ b/test/674-hiddenapi/src-art/Main.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dalvik.system.InMemoryDexClassLoader; +import dalvik.system.PathClassLoader; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + prepareNativeLibFileName(args[0]); + + // Run test with both parent and child dex files loaded with class loaders. + // The expectation is that hidden members in parent should be visible to + // the child. + doTest(false, false); + doUnloading(); + + // Now append parent dex file to boot class path and run again. This time + // the child dex file should not be able to access private APIs of the parent. + appendToBootClassLoader(DEX_PARENT_BOOT); + doTest(true, false); + doUnloading(); + + // And finally append to child to boot class path as well. With both in the + // boot class path, access should be granted. + appendToBootClassLoader(DEX_CHILD); + doTest(true, true); + doUnloading(); + } + + private static void doTest(boolean parentInBoot, boolean childInBoot) throws Exception { + // Load parent dex if it is not in boot class path. + ClassLoader parentLoader = null; + if (parentInBoot) { + parentLoader = BOOT_CLASS_LOADER; + } else { + parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader()); + } + + // Load child dex if it is not in boot class path. + ClassLoader childLoader = null; + if (childInBoot) { + if (parentLoader != BOOT_CLASS_LOADER) { + throw new IllegalStateException( + "DeclaringClass must be in parent class loader of CallingClass"); + } + childLoader = BOOT_CLASS_LOADER; + } else { + childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader); + } + + // Create a unique copy of the native library. Each shared library can only + // be loaded once, but for some reason even classes from a class loader + // cannot register their native methods against symbols in a shared library + // loaded by their parent class loader. + String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot); + + // Invoke ChildClass.runTest + Class.forName("ChildClass", true, childLoader) + .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE) + .invoke(null, nativeLibCopy, parentInBoot, childInBoot); + } + + // Routine which tries to figure out the absolute path of our native library. + private static void prepareNativeLibFileName(String arg) throws Exception { + String libName = System.mapLibraryName(arg); + Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths"); + libPathsMethod.setAccessible(true); + String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime()); + nativeLibFileName = null; + for (String p : libPaths) { + String candidate = p + libName; + if (new File(candidate).exists()) { + nativeLibFileName = candidate; + break; + } + } + if (nativeLibFileName == null) { + throw new IllegalStateException("Didn't find " + libName + " in " + + Arrays.toString(libPaths)); + } + } + + // Helper to read dex file into memory. + private static ByteBuffer readDexFile(String jarFileName) throws Exception { + ZipFile zip = new ZipFile(new File(jarFileName)); + ZipEntry entry = zip.getEntry("classes.dex"); + InputStream is = zip.getInputStream(entry); + int offset = 0; + int size = (int) entry.getSize(); + ByteBuffer buffer = ByteBuffer.allocate(size); + while (is.available() > 0) { + is.read(buffer.array(), offset, size - offset); + } + is.close(); + zip.close(); + return buffer; + } + + // Copy native library to a new file with a unique name so it does not conflict + // with other loaded instance of the same binary file. + private static String createNativeLibCopy(boolean parentInBoot, boolean childInBoot) + throws Exception { + String tempFileName = System.mapLibraryName( + "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0")); + File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName); + Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath()); + return tempFile.getAbsolutePath(); + } + + private static void doUnloading() { + // Do multiple GCs to prevent rare flakiness if some other thread is keeping the + // classloader live. + for (int i = 0; i < 5; ++i) { + Runtime.getRuntime().gc(); + } + } + + private static String nativeLibFileName; + + private static final String DEX_PARENT = + new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath(); + private static final String DEX_PARENT_BOOT = + new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath(); + private static final String DEX_CHILD = + new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath(); + + private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader(); + + private static native void appendToBootClassLoader(String dexPath); +} diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java new file mode 100644 index 0000000000..af615bfd5f --- /dev/null +++ b/test/674-hiddenapi/src-ex/ChildClass.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVisitor; + +public class ChildClass { + enum PrimitiveType { + TInteger('I', Integer.TYPE, Integer.valueOf(0)), + TLong('J', Long.TYPE, Long.valueOf(0)), + TFloat('F', Float.TYPE, Float.valueOf(0)), + TDouble('D', Double.TYPE, Double.valueOf(0)), + TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)), + TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)), + TShort('S', Short.TYPE, Short.valueOf((short) 0)), + TCharacter('C', Character.TYPE, Character.valueOf('0')); + + PrimitiveType(char shorty, Class klass, Object value) { + mShorty = shorty; + mClass = klass; + mDefaultValue = value; + } + + public char mShorty; + public Class mClass; + public Object mDefaultValue; + } + + enum Hiddenness { + Whitelist(PrimitiveType.TShort), + LightGreylist(PrimitiveType.TBoolean), + DarkGreylist(PrimitiveType.TByte), + Blacklist(PrimitiveType.TCharacter); + + Hiddenness(PrimitiveType type) { mAssociatedType = type; } + public PrimitiveType mAssociatedType; + } + + enum Visibility { + Public(PrimitiveType.TInteger), + Package(PrimitiveType.TFloat), + Protected(PrimitiveType.TLong), + Private(PrimitiveType.TDouble); + + Visibility(PrimitiveType type) { mAssociatedType = type; } + public PrimitiveType mAssociatedType; + } + + enum Behaviour { + Granted, + Warning, + Denied, + } + + private static final boolean booleanValues[] = new boolean[] { false, true }; + + public static void runTest(String libFileName, boolean expectedParentInBoot, + boolean expectedChildInBoot) throws Exception { + System.load(libFileName); + + // Check expectations about loading into boot class path. + isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null); + if (isParentInBoot != expectedParentInBoot) { + throw new RuntimeException("Expected ParentClass " + + (expectedParentInBoot ? "" : "not ") + "in boot class path"); + } + isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null); + if (isChildInBoot != expectedChildInBoot) { + throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") + + "in boot class path"); + } + + // Run meaningful combinations of access flags. + for (Hiddenness hiddenness : Hiddenness.values()) { + final Behaviour expected = Behaviour.Granted; + + for (boolean isStatic : booleanValues) { + String suffix = (isStatic ? "Static" : "") + hiddenness.name(); + + for (Visibility visibility : Visibility.values()) { + // Test reflection and JNI on methods and fields + for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) { + String baseName = visibility.name() + suffix; + checkField(klass, "field" + baseName, isStatic, visibility, expected); + checkMethod(klass, "method" + baseName, isStatic, visibility, expected); + } + + // Check whether one can use a class constructor. + checkConstructor(ParentClass.class, visibility, hiddenness, expected); + + // Check whether you can use an interface default method. + String name = "method" + visibility.name() + "Default" + hiddenness.name(); + checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected); + } + + // Test whether static linking succeeds. + checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected); + checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected); + checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected); + } + + // Check whether Class.newInstance succeeds. + checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected); + } + } + + private static void checkField(Class<?> klass, String name, boolean isStatic, + Visibility visibility, Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + if (klass.isInterface() && (!isStatic || !isPublic)) { + // Interfaces only have public static fields. + return; + } + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover); + } + + if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) { + throwDiscoveryException(klass, name, true, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test that modifiers are unaffected. + + if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, true); + } + + // Test getters and setters when meaningful. + + clearWarning(); + if (!Reflection.canGetField(klass, name)) { + throwAccessException(klass, name, true, "Field.getInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.getInt()", setsWarning); + } + + clearWarning(); + if (!Reflection.canSetField(klass, name)) { + throwAccessException(klass, name, true, "Field.setInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.setInt()", setsWarning); + } + + clearWarning(); + if (!JNI.canGetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "getIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "getIntField", setsWarning); + } + + clearWarning(); + if (!JNI.canSetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "setIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "setIntField", setsWarning); + } + } + + private static void checkMethod(Class<?> klass, String name, boolean isStatic, + Visibility visibility, Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + if (klass.isInterface() && !isPublic) { + // All interface members are public. + return; + } + + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover); + } + + if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) { + throwDiscoveryException(klass, name, false, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test that modifiers are unaffected. + + if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, false); + } + + // Test whether we can invoke the method. This skips non-static interface methods. + + if (!klass.isInterface() || isStatic) { + clearWarning(); + if (!Reflection.canInvokeMethod(klass, name)) { + throwAccessException(klass, name, false, "invoke()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "invoke()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeMethodA(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodA"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodA()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeMethodV(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodV"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodV()", setsWarning); + } + } + } + + private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, + Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + String signature = "(" + visibility.mAssociatedType.mShorty + + hiddenness.mAssociatedType.mShorty + ")V"; + String fullName = "<init>" + signature; + Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass, + hiddenness.mAssociatedType.mClass }; + Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue, + hiddenness.mAssociatedType.mDefaultValue }; + + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover); + } + + if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) { + throwDiscoveryException( + klass, fullName, false, "getConstructor()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) { + throwDiscoveryException( + klass, fullName, false, "getConstructors()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test whether we can invoke the constructor. + + clearWarning(); + if (!Reflection.canInvokeConstructor(klass, args, initargs)) { + throwAccessException(klass, fullName, false, "invoke()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "invoke()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeConstructorA(klass, signature)) { + throwAccessException(klass, fullName, false, "NewObjectA"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "NewObjectA", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeConstructorV(klass, signature)) { + throwAccessException(klass, fullName, false, "NewObjectV"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "NewObjectV", setsWarning); + } + } + + private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour) + throws Exception { + boolean canAccess = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + clearWarning(); + if (Reflection.canUseNewInstance(klass) != canAccess) { + throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + + "be able to construct " + klass.getName() + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + if (canAccess && hasPendingWarning() != setsWarning) { + throwWarningException(klass, "nullary constructor", false, "newInstance", setsWarning); + } + } + + private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour) + throws Exception { + boolean canAccess = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + clearWarning(); + if (Linking.canAccess(className, takesParameter) != canAccess) { + throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + + "be able to verify " + className + "." + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + if (canAccess && hasPendingWarning() != setsWarning) { + throwWarningException( + Class.forName(className), "access", false, "static linking", setsWarning); + } + } + + private static void throwDiscoveryException(Class<?> klass, String name, boolean isField, + String fn, boolean canAccess) { + throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + + "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwAccessException(Class<?> klass, String name, boolean isField, + String fn) { + throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") + + klass.getName() + "." + name + " using " + fn + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwWarningException(Class<?> klass, String name, boolean isField, + String fn, boolean setsWarning) { + throw new RuntimeException("Expected access to " + (isField ? "field " : "method ") + + klass.getName() + "." + name + " using " + fn + " to " + (setsWarning ? "" : "not ") + + "set the warning flag. " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwModifiersException(Class<?> klass, String name, boolean isField) { + throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + + "." + name + " to not expose hidden modifiers"); + } + + private static boolean isParentInBoot; + private static boolean isChildInBoot; + + private static native boolean hasPendingWarning(); + private static native void clearWarning(); +} diff --git a/test/674-hiddenapi/src-ex/JNI.java b/test/674-hiddenapi/src-ex/JNI.java new file mode 100644 index 0000000000..5dfb2963fa --- /dev/null +++ b/test/674-hiddenapi/src-ex/JNI.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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 JNI { + public static native boolean canDiscoverField(Class<?> klass, String name, boolean isStatic); + public static native boolean canGetField(Class<?> klass, String name, boolean isStatic); + public static native boolean canSetField(Class<?> klass, String name, boolean isStatic); + + public static native boolean canDiscoverMethod(Class<?> klass, String name, boolean isStatic); + public static native boolean canInvokeMethodA(Class<?> klass, String name, boolean isStatic); + public static native boolean canInvokeMethodV(Class<?> klass, String name, boolean isStatic); + + public static native boolean canDiscoverConstructor(Class<?> klass, String signature); + public static native boolean canInvokeConstructorA(Class<?> klass, String signature); + public static native boolean canInvokeConstructorV(Class<?> klass, String signature); +} diff --git a/test/674-hiddenapi/src-ex/Linking.java b/test/674-hiddenapi/src-ex/Linking.java new file mode 100644 index 0000000000..c6735d85fe --- /dev/null +++ b/test/674-hiddenapi/src-ex/Linking.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2018 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; + +public class Linking { + public static boolean canAccess(String className, boolean takesParameter) throws Exception { + try { + Class<?> c = Class.forName(className); + if (takesParameter) { + c.getDeclaredMethod("access", Integer.TYPE).invoke(null, 42); + } else { + c.getDeclaredMethod("access").invoke(null); + } + return true; + } catch (InvocationTargetException ex) { + if (ex.getCause() instanceof IllegalAccessError) { + return false; + } else { + throw ex; + } + } + } +} + +// INSTANCE FIELD GET + +class LinkFieldGetWhitelist { + public static int access() { + return new ParentClass().fieldPublicWhitelist; + } +} + +class LinkFieldGetLightGreylist { + public static int access() { + return new ParentClass().fieldPublicLightGreylist; + } +} + +class LinkFieldGetDarkGreylist { + public static int access() { + return new ParentClass().fieldPublicDarkGreylist; + } +} + +class LinkFieldGetBlacklist { + public static int access() { + return new ParentClass().fieldPublicBlacklist; + } +} + +// INSTANCE FIELD SET + +class LinkFieldSetWhitelist { + public static void access(int x) { + new ParentClass().fieldPublicWhitelist = x; + } +} + +class LinkFieldSetLightGreylist { + public static void access(int x) { + new ParentClass().fieldPublicLightGreylist = x; + } +} + +class LinkFieldSetDarkGreylist { + public static void access(int x) { + new ParentClass().fieldPublicDarkGreylist = x; + } +} + +class LinkFieldSetBlacklist { + public static void access(int x) { + new ParentClass().fieldPublicBlacklist = x; + } +} + +// STATIC FIELD GET + +class LinkFieldGetStaticWhitelist { + public static int access() { + return ParentClass.fieldPublicStaticWhitelist; + } +} + +class LinkFieldGetStaticLightGreylist { + public static int access() { + return ParentClass.fieldPublicStaticLightGreylist; + } +} + +class LinkFieldGetStaticDarkGreylist { + public static int access() { + return ParentClass.fieldPublicStaticDarkGreylist; + } +} + +class LinkFieldGetStaticBlacklist { + public static int access() { + return ParentClass.fieldPublicStaticBlacklist; + } +} + +// STATIC FIELD SET + +class LinkFieldSetStaticWhitelist { + public static void access(int x) { + ParentClass.fieldPublicStaticWhitelist = x; + } +} + +class LinkFieldSetStaticLightGreylist { + public static void access(int x) { + ParentClass.fieldPublicStaticLightGreylist = x; + } +} + +class LinkFieldSetStaticDarkGreylist { + public static void access(int x) { + ParentClass.fieldPublicStaticDarkGreylist = x; + } +} + +class LinkFieldSetStaticBlacklist { + public static void access(int x) { + ParentClass.fieldPublicStaticBlacklist = x; + } +} + +// INVOKE INSTANCE METHOD + +class LinkMethodWhitelist { + public static int access() { + return new ParentClass().methodPublicWhitelist(); + } +} + +class LinkMethodLightGreylist { + public static int access() { + return new ParentClass().methodPublicLightGreylist(); + } +} + +class LinkMethodDarkGreylist { + public static int access() { + return new ParentClass().methodPublicDarkGreylist(); + } +} + +class LinkMethodBlacklist { + public static int access() { + return new ParentClass().methodPublicBlacklist(); + } +} + +// INVOKE STATIC METHOD + +class LinkMethodStaticWhitelist { + public static int access() { + return ParentClass.methodPublicStaticWhitelist(); + } +} + +class LinkMethodStaticLightGreylist { + public static int access() { + return ParentClass.methodPublicStaticLightGreylist(); + } +} + +class LinkMethodStaticDarkGreylist { + public static int access() { + return ParentClass.methodPublicStaticDarkGreylist(); + } +} + +class LinkMethodStaticBlacklist { + public static int access() { + return ParentClass.methodPublicStaticBlacklist(); + } +} diff --git a/test/674-hiddenapi/src-ex/Reflection.java b/test/674-hiddenapi/src-ex/Reflection.java new file mode 100644 index 0000000000..3667e91611 --- /dev/null +++ b/test/674-hiddenapi/src-ex/Reflection.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +public class Reflection { + public static boolean canDiscoverWithGetDeclaredField(Class<?> klass, String name) { + try { + klass.getDeclaredField(name); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredFields(Class<?> klass, String name) { + for (Field f : klass.getDeclaredFields()) { + if (f.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetField(Class<?> klass, String name) { + try { + klass.getField(name); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetFields(Class<?> klass, String name) { + for (Field f : klass.getFields()) { + if (f.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canGetField(Class<?> klass, String name) { + try { + Field f = klass.getDeclaredField(name); + f.setAccessible(true); + f.getInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance()); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canSetField(Class<?> klass, String name) { + try { + Field f = klass.getDeclaredField(name); + f.setAccessible(true); + f.setInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance(), 42); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredMethod(Class<?> klass, String name) { + try { + klass.getDeclaredMethod(name); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredMethods(Class<?> klass, String name) { + for (Method m : klass.getDeclaredMethods()) { + if (m.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetMethod(Class<?> klass, String name) { + try { + klass.getMethod(name); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetMethods(Class<?> klass, String name) { + for (Method m : klass.getMethods()) { + if (m.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canInvokeMethod(Class<?> klass, String name) { + try { + Method m = klass.getDeclaredMethod(name); + m.setAccessible(true); + m.invoke(klass.isInterface() ? null : klass.newInstance()); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredConstructor(Class<?> klass, Class<?> args[]) { + try { + klass.getDeclaredConstructor(args); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredConstructors(Class<?> klass, Class<?> args[]) { + for (Constructor c : klass.getDeclaredConstructors()) { + if (Arrays.equals(c.getParameterTypes(), args)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetConstructor(Class<?> klass, Class<?> args[]) { + try { + klass.getConstructor(args); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetConstructors(Class<?> klass, Class<?> args[]) { + for (Constructor c : klass.getConstructors()) { + if (Arrays.equals(c.getParameterTypes(), args)) { + return true; + } + } + return false; + } + + public static boolean canInvokeConstructor(Class<?> klass, Class<?> args[], Object[] initargs) { + try { + Constructor c = klass.getDeclaredConstructor(args); + c.setAccessible(true); + c.newInstance(initargs); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canUseNewInstance(Class<?> klass) throws IllegalAccessException { + try { + klass.newInstance(); + return true; + } catch (InstantiationException ex) { + return false; + } + } + + private static native int getHiddenApiAccessFlags(); + + public static boolean canObserveFieldHiddenAccessFlags(Class<?> klass, String name) + throws Exception { + return (klass.getDeclaredField(name).getModifiers() & getHiddenApiAccessFlags()) != 0; + } + + public static boolean canObserveMethodHiddenAccessFlags(Class<?> klass, String name) + throws Exception { + return (klass.getDeclaredMethod(name).getModifiers() & getHiddenApiAccessFlags()) != 0; + } + + public static boolean canObserveConstructorHiddenAccessFlags(Class<?> klass, Class<?> args[]) + throws Exception { + return (klass.getConstructor(args).getModifiers() & getHiddenApiAccessFlags()) != 0; + } +} diff --git a/test/674-hiddenapi/src/NullaryConstructorBlacklist.java b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java new file mode 100644 index 0000000000..5bf6278a77 --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorBlacklist { + public NullaryConstructorBlacklist() { x = 22; } + public NullaryConstructorBlacklist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java new file mode 100644 index 0000000000..c25a767d1d --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorDarkGreylist { + public NullaryConstructorDarkGreylist() { x = 22; } + public NullaryConstructorDarkGreylist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java new file mode 100644 index 0000000000..d5dac8b7c0 --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorLightGreylist { + public NullaryConstructorLightGreylist() { x = 22; } + public NullaryConstructorLightGreylist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorWhitelist.java b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java new file mode 100644 index 0000000000..d1019077cf --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorWhitelist { + public NullaryConstructorWhitelist() { x = 22; } + public NullaryConstructorWhitelist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/ParentClass.java b/test/674-hiddenapi/src/ParentClass.java new file mode 100644 index 0000000000..edad02dc2c --- /dev/null +++ b/test/674-hiddenapi/src/ParentClass.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class ParentClass { + public ParentClass() {} + + // INSTANCE FIELD + + public int fieldPublicWhitelist = 211; + int fieldPackageWhitelist = 212; + protected int fieldProtectedWhitelist = 213; + private int fieldPrivateWhitelist = 214; + + public int fieldPublicLightGreylist = 221; + int fieldPackageLightGreylist = 222; + protected int fieldProtectedLightGreylist = 223; + private int fieldPrivateLightGreylist = 224; + + public int fieldPublicDarkGreylist = 231; + int fieldPackageDarkGreylist = 232; + protected int fieldProtectedDarkGreylist = 233; + private int fieldPrivateDarkGreylist = 234; + + public int fieldPublicBlacklist = 241; + int fieldPackageBlacklist = 242; + protected int fieldProtectedBlacklist = 243; + private int fieldPrivateBlacklist = 244; + + // STATIC FIELD + + public static int fieldPublicStaticWhitelist = 111; + static int fieldPackageStaticWhitelist = 112; + protected static int fieldProtectedStaticWhitelist = 113; + private static int fieldPrivateStaticWhitelist = 114; + + public static int fieldPublicStaticLightGreylist = 121; + static int fieldPackageStaticLightGreylist = 122; + protected static int fieldProtectedStaticLightGreylist = 123; + private static int fieldPrivateStaticLightGreylist = 124; + + public static int fieldPublicStaticDarkGreylist = 131; + static int fieldPackageStaticDarkGreylist = 132; + protected static int fieldProtectedStaticDarkGreylist = 133; + private static int fieldPrivateStaticDarkGreylist = 134; + + public static int fieldPublicStaticBlacklist = 141; + static int fieldPackageStaticBlacklist = 142; + protected static int fieldProtectedStaticBlacklist = 143; + private static int fieldPrivateStaticBlacklist = 144; + + // INSTANCE METHOD + + public int methodPublicWhitelist() { return 411; } + int methodPackageWhitelist() { return 412; } + protected int methodProtectedWhitelist() { return 413; } + private int methodPrivateWhitelist() { return 414; } + + public int methodPublicLightGreylist() { return 421; } + int methodPackageLightGreylist() { return 422; } + protected int methodProtectedLightGreylist() { return 423; } + private int methodPrivateLightGreylist() { return 424; } + + public int methodPublicDarkGreylist() { return 431; } + int methodPackageDarkGreylist() { return 432; } + protected int methodProtectedDarkGreylist() { return 433; } + private int methodPrivateDarkGreylist() { return 434; } + + public int methodPublicBlacklist() { return 441; } + int methodPackageBlacklist() { return 442; } + protected int methodProtectedBlacklist() { return 443; } + private int methodPrivateBlacklist() { return 444; } + + // STATIC METHOD + + public static int methodPublicStaticWhitelist() { return 311; } + static int methodPackageStaticWhitelist() { return 312; } + protected static int methodProtectedStaticWhitelist() { return 313; } + private static int methodPrivateStaticWhitelist() { return 314; } + + public static int methodPublicStaticLightGreylist() { return 321; } + static int methodPackageStaticLightGreylist() { return 322; } + protected static int methodProtectedStaticLightGreylist() { return 323; } + private static int methodPrivateStaticLightGreylist() { return 324; } + + public static int methodPublicStaticDarkGreylist() { return 331; } + static int methodPackageStaticDarkGreylist() { return 332; } + protected static int methodProtectedStaticDarkGreylist() { return 333; } + private static int methodPrivateStaticDarkGreylist() { return 334; } + + public static int methodPublicStaticBlacklist() { return 341; } + static int methodPackageStaticBlacklist() { return 342; } + protected static int methodProtectedStaticBlacklist() { return 343; } + private static int methodPrivateStaticBlacklist() { return 344; } + + // CONSTRUCTOR + + // Whitelist + public ParentClass(int x, short y) {} + ParentClass(float x, short y) {} + protected ParentClass(long x, short y) {} + private ParentClass(double x, short y) {} + + // Light greylist + public ParentClass(int x, boolean y) {} + ParentClass(float x, boolean y) {} + protected ParentClass(long x, boolean y) {} + private ParentClass(double x, boolean y) {} + + // Dark greylist + public ParentClass(int x, byte y) {} + ParentClass(float x, byte y) {} + protected ParentClass(long x, byte y) {} + private ParentClass(double x, byte y) {} + + // Blacklist + public ParentClass(int x, char y) {} + ParentClass(float x, char y) {} + protected ParentClass(long x, char y) {} + private ParentClass(double x, char y) {} +} diff --git a/test/674-hiddenapi/src/ParentInterface.java b/test/674-hiddenapi/src/ParentInterface.java new file mode 100644 index 0000000000..e36fe0e6b2 --- /dev/null +++ b/test/674-hiddenapi/src/ParentInterface.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface ParentInterface { + // STATIC FIELD + static int fieldPublicStaticWhitelist = 11; + static int fieldPublicStaticLightGreylist = 12; + static int fieldPublicStaticDarkGreylist = 13; + static int fieldPublicStaticBlacklist = 14; + + // INSTANCE METHOD + int methodPublicWhitelist(); + int methodPublicBlacklist(); + int methodPublicLightGreylist(); + int methodPublicDarkGreylist(); + + // STATIC METHOD + static int methodPublicStaticWhitelist() { return 21; } + static int methodPublicStaticLightGreylist() { return 22; } + static int methodPublicStaticDarkGreylist() { return 23; } + static int methodPublicStaticBlacklist() { return 24; } + + // DEFAULT METHOD + default int methodPublicDefaultWhitelist() { return 31; } + default int methodPublicDefaultLightGreylist() { return 32; } + default int methodPublicDefaultDarkGreylist() { return 33; } + default int methodPublicDefaultBlacklist() { return 34; } +} diff --git a/test/Android.bp b/test/Android.bp index 49a34a1246..470a68f386 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -389,6 +389,7 @@ cc_defaults { "661-oat-writer-layout/oat_writer_layout.cc", "664-aget-verifier/aget-verifier.cc", "667-jit-jni-stub/jit_jni_stub_test.cc", + "674-hiddenapi/hiddenapi.cc", "708-jit-cache-churn/jit.cc", ], shared_libs: [ diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 2cada76d90..2df0cc6fae 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -20,6 +20,7 @@ include art/build/Android.common_test.mk # Dependencies for actually running a run-test. TEST_ART_RUN_TEST_DEPENDENCIES := \ $(HOST_OUT_EXECUTABLES)/dx \ + $(HOST_OUT_EXECUTABLES)/hiddenapi \ $(HOST_OUT_EXECUTABLES)/jasmin \ $(HOST_OUT_EXECUTABLES)/smali \ $(HOST_OUT_EXECUTABLES)/dexmerger \ diff --git a/test/etc/default-build b/test/etc/default-build index 5c8257f210..4dc2393c54 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -81,6 +81,17 @@ else HAS_SRC_DEX2OAT_UNRESOLVED=false fi +if [ -f api-light-greylist.txt -o -f api-dark-greylist.txt -o -f api-blacklist.txt ]; then + HAS_HIDDENAPI_SPEC=true +else + HAS_HIDDENAPI_SPEC=false +fi + +# USE_HIDDENAPI=false run-test... will disable hiddenapi. +if [ -z "${USE_HIDDENAPI}" ]; then + USE_HIDDENAPI=true +fi + # DESUGAR=false run-test... will disable desugar. if [[ "$DESUGAR" == false ]]; then USE_DESUGAR=false @@ -321,6 +332,24 @@ function make_dexmerge() { ${DXMERGER} "$dst_file" "${dex_files_to_merge[@]}" } +function make_hiddenapi() { + local args=() + while [[ $# -gt 0 ]]; do + args+=("--dex=$1") + shift + done + if [ -f api-light-greylist.txt ]; then + args+=("--light-greylist=api-light-greylist.txt") + fi + if [ -f api-dark-greylist.txt ]; then + args+=("--dark-greylist=api-dark-greylist.txt") + fi + if [ -f api-blacklist.txt ]; then + args+=("--blacklist=api-blacklist.txt") + fi + ${HIDDENAPI} "${args[@]}" +} + # Print the directory name only if it exists. function maybe_dir() { local dirname="$1" @@ -334,6 +363,13 @@ if [ -e classes.dex ]; then exit 0 fi +# Helper function for a common test. Evaluate with $(has_mutlidex). +function has_multidex() { + echo [ ${HAS_SRC_MULTIDEX} = "true" \ + -o ${HAS_JASMIN_MULTIDEX} = "true" \ + -o ${HAS_SMALI_MULTIDEX} = "true" ] +} + if [ ${HAS_SRC_DEX2OAT_UNRESOLVED} = "true" ]; then mkdir classes mkdir classes-ex @@ -501,9 +537,18 @@ if [ ${HAS_SRC_EX} = "true" ]; then fi fi +# Apply hiddenapi on the dex files if the test has API list file(s). +if [ ${NEED_DEX} = "true" -a ${USE_HIDDENAPI} = "true" -a ${HAS_HIDDENAPI_SPEC} = "true" ]; then + if $(has_multidex); then + make_hiddenapi classes.dex classes2.dex + else + make_hiddenapi classes.dex + fi +fi + # Create a single dex jar with two dex files for multidex. if [ ${NEED_DEX} = "true" ]; then - if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_JASMIN_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then + if $(has_multidex); then zip $TEST_NAME.jar classes.dex classes2.dex else zip $TEST_NAME.jar classes.dex diff --git a/test/knownfailures.json b/test/knownfailures.json index 41d976a174..27bec3e965 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -256,11 +256,6 @@ "testing deoptimizing at quick-to-interpreter bridge."] }, { - "tests": "137-cfi", - "description": ["CFI unwinding expects managed frames"], - "variant": "interpreter" - }, - { "tests": "906-iterate-heap", "description": ["Test 906 iterates the heap filtering with different", "options. No instances should be created between those", diff --git a/test/run-test b/test/run-test index 75fe15c919..a453f22e29 100755 --- a/test/run-test +++ b/test/run-test @@ -113,6 +113,12 @@ if [ -z "$ZIPALIGN" ]; then fi export ZIPALIGN +# If hiddenapi was not set by the environment variable, assume it is in +# ANDROID_HOST_OUT. +if [ -z "$HIDDENAPI" ]; then + export HIDDENAPI="${ANDROID_HOST_OUT}/bin/hiddenapi" +fi + info="info.txt" build="build" run="run" diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index fafa1afcda..e46abb6f5a 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -22,7 +22,7 @@ namespace art { -// Taken from art/runtime/modifiers.h +// Taken from art/runtime/dex/modifiers.h static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f); diff --git a/tools/cpp-define-generator/constant_class.def b/tools/cpp-define-generator/constant_class.def index 4f1d875e5a..1310103ab7 100644 --- a/tools/cpp-define-generator/constant_class.def +++ b/tools/cpp-define-generator/constant_class.def @@ -15,8 +15,8 @@ */ #if defined(DEFINE_INCLUDE_DEPENDENCIES) -#include "modifiers.h" // kAccClassIsFinalizable #include "base/bit_utils.h" // MostSignificantBit +#include "dex/modifiers.h" // kAccClassIsFinalizable #endif #define DEFINE_FLAG_OFFSET(type_name, field_name, expr) \ diff --git a/tools/cpp-define-generator/constant_globals.def b/tools/cpp-define-generator/constant_globals.def index 5018f52937..539633e0b3 100644 --- a/tools/cpp-define-generator/constant_globals.def +++ b/tools/cpp-define-generator/constant_globals.def @@ -18,8 +18,8 @@ #if defined(DEFINE_INCLUDE_DEPENDENCIES) #include <atomic> // std::memory_order_relaxed +#include "dex/modifiers.h" #include "globals.h" // art::kObjectAlignment -#include "modifiers.h" #endif DEFINE_EXPR(STD_MEMORY_ORDER_RELAXED, int32_t, std::memory_order_relaxed) |