diff options
87 files changed, 3219 insertions, 379 deletions
diff --git a/Android.bp b/Android.bp index 197860694b..caf4f9a325 100644 --- a/Android.bp +++ b/Android.bp @@ -47,6 +47,7 @@ subdirs = [ "tools/breakpoint-logger", "tools/cpp-define-generator", "tools/dmtracedump", + "tools/hiddenapi", "tools/titrace", "tools/wrapagentproperties", ] diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 45f4e2d4d8..4f5df03c19 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -36,6 +36,7 @@ GTEST_DEX_DIRECTORIES := \ ForClassLoaderD \ ExceptionHandle \ GetMethodSignature \ + HiddenApi \ ImageLayoutA \ ImageLayoutB \ IMTA \ @@ -113,6 +114,7 @@ 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_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 ART_GTEST_image_test_DEX_DEPS := ImageLayoutA ImageLayoutB DefaultMethods ART_GTEST_imtable_test_DEX_DEPS := IMTA IMTB ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation @@ -266,6 +268,11 @@ ART_GTEST_patchoat_test_TARGET_DEPS := \ ART_GTEST_profile_assistant_test_HOST_DEPS := profmand-host ART_GTEST_profile_assistant_test_TARGET_DEPS := profmand-target +ART_GTEST_hiddenapi_test_HOST_DEPS := \ + $(HOST_CORE_IMAGE_DEFAULT_64) \ + $(HOST_CORE_IMAGE_DEFAULT_32) \ + hiddenapid-host + # The path for which all the source files are relative, not actually the current directory. LOCAL_PATH := art @@ -279,6 +286,7 @@ ART_TEST_MODULES := \ art_dexlayout_tests \ art_dexlist_tests \ art_dexoptanalyzer_tests \ + art_hiddenapi_tests \ art_imgdiag_tests \ art_oatdump_tests \ art_patchoat_tests \ diff --git a/compiler/driver/compiled_method_storage.cc b/compiler/driver/compiled_method_storage.cc index c8c2b6998f..48477abe5b 100644 --- a/compiler/driver/compiled_method_storage.cc +++ b/compiler/driver/compiled_method_storage.cc @@ -137,16 +137,7 @@ class CompiledMethodStorage::DedupeHashFunc { return hash; } else { - size_t hash = 0x811c9dc5; - for (uint32_t i = 0; i < len; ++i) { - hash = (hash * 16777619) ^ data[i]; - } - hash += hash << 13; - hash ^= hash >> 7; - hash += hash << 3; - hash ^= hash >> 17; - hash += hash << 5; - return hash; + return HashBytes(data, len); } } }; diff --git a/compiler/linker/elf_builder.h b/compiler/linker/elf_builder.h index 3145497091..1c875189c5 100644 --- a/compiler/linker/elf_builder.h +++ b/compiler/linker/elf_builder.h @@ -18,6 +18,7 @@ #define ART_COMPILER_LINKER_ELF_BUILDER_H_ #include <vector> +#include <unordered_map> #include "arch/instruction_set.h" #include "arch/mips/instruction_set_features_mips.h" @@ -309,27 +310,24 @@ class ElfBuilder FINAL { /* info */ 0, align, /* entsize */ 0), - current_offset_(0), - last_offset_(0) { + current_offset_(0) { } Elf_Word Write(const std::string& name) { if (current_offset_ == 0) { DCHECK(name.empty()); - } else if (name == last_name_) { - return last_offset_; // Very simple string de-duplication. } - last_name_ = name; - last_offset_ = current_offset_; - this->WriteFully(name.c_str(), name.length() + 1); - current_offset_ += name.length() + 1; - return last_offset_; + auto res = written_names_.emplace(name, current_offset_); + if (res.second) { // Inserted. + this->WriteFully(name.c_str(), name.length() + 1); + current_offset_ += name.length() + 1; + } + return res.first->second; // Offset. } private: Elf_Word current_offset_; - std::string last_name_; - Elf_Word last_offset_; + std::unordered_map<std::string, Elf_Word> written_names_; // Dedup strings. }; // Writer of .dynsym and .symtab sections. diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 28f481670c..13bbffa1e3 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -3491,7 +3491,11 @@ void InstructionCodeGeneratorARM64::VisitFloatConstant(HFloatConstant* constant } void InstructionCodeGeneratorARM64::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } + HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); HLoopInformation* info = block->GetLoopInformation(); diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index f1ad4e187e..577fe00dcd 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -2776,7 +2776,11 @@ void CodeGeneratorARMVIXL::InvokeRuntimeWithoutRecordingPcInfo(int32_t entry_poi } void InstructionCodeGeneratorARMVIXL::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } + HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); HLoopInformation* info = block->GetLoopInformation(); diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index c8bd5d4fc8..5c8e46ed19 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -4034,7 +4034,11 @@ void LocationsBuilderMIPS::VisitGoto(HGoto* got) { } void InstructionCodeGeneratorMIPS::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } + HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); HLoopInformation* info = block->GetLoopInformation(); diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index bbdc3be5c1..bcfe051c90 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -3562,7 +3562,11 @@ void InstructionCodeGeneratorMIPS64::VisitFloatConstant(HFloatConstant* constant } void InstructionCodeGeneratorMIPS64::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } + HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); HLoopInformation* info = block->GetLoopInformation(); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 537e97aacf..cbe9e0a35c 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -1347,7 +1347,10 @@ void CodeGeneratorX86::AddLocationAsTemp(Location location, LocationSummary* loc } void InstructionCodeGeneratorX86::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 4a6428592e..510eec4f30 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1449,7 +1449,10 @@ void CodeGeneratorX86_64::AddLocationAsTemp(Location location, LocationSummary* } void InstructionCodeGeneratorX86_64::HandleGoto(HInstruction* got, HBasicBlock* successor) { - DCHECK(!successor->IsExitBlock()); + if (successor->IsExitBlock()) { + DCHECK(got->GetPrevious()->AlwaysThrows()); + return; // no code needed + } HBasicBlock* block = got->GetBlock(); HInstruction* previous = got->GetPrevious(); diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc index d8ebac95a8..f4760d661f 100644 --- a/compiler/optimizing/code_sinking.cc +++ b/compiler/optimizing/code_sinking.cc @@ -34,7 +34,9 @@ void CodeSinking::Run() { // TODO(ngeoffray): we do not profile branches yet, so use throw instructions // as an indicator of an uncommon branch. for (HBasicBlock* exit_predecessor : exit->GetPredecessors()) { - if (exit_predecessor->GetLastInstruction()->IsThrow()) { + HInstruction* last = exit_predecessor->GetLastInstruction(); + // Any predecessor of the exit that does not return, throws an exception. + if (!last->IsReturn() && !last->IsReturnVoid()) { SinkCodeToUncommonBranch(exit_predecessor); } } diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc index 3cc7b0e78d..cca1055ac8 100644 --- a/compiler/optimizing/dead_code_elimination.cc +++ b/compiler/optimizing/dead_code_elimination.cc @@ -148,6 +148,77 @@ static HConstant* Evaluate(HCondition* condition, HInstruction* left, HInstructi // Simplify the pattern: // +// B1 +// / \ +// | foo() // always throws +// \ goto B2 +// \ / +// B2 +// +// Into: +// +// B1 +// / \ +// | foo() +// | goto Exit +// | | +// B2 Exit +// +// Rationale: +// Removal of the never taken edge to B2 may expose +// other optimization opportunities, such as code sinking. +bool HDeadCodeElimination::SimplifyAlwaysThrows() { + // Make sure exceptions go to exit. + if (graph_->HasTryCatch()) { + return false; + } + HBasicBlock* exit = graph_->GetExitBlock(); + if (exit == nullptr) { + return false; + } + + bool rerun_dominance_and_loop_analysis = false; + + // Order does not matter, just pick one. + for (HBasicBlock* block : graph_->GetReversePostOrder()) { + HInstruction* first = block->GetFirstInstruction(); + HInstruction* last = block->GetLastInstruction(); + // Ensure only one throwing instruction appears before goto. + if (first->AlwaysThrows() && + first->GetNext() == last && + last->IsGoto() && + block->GetPhis().IsEmpty() && + block->GetPredecessors().size() == 1u) { + DCHECK_EQ(block->GetSuccessors().size(), 1u); + HBasicBlock* pred = block->GetSinglePredecessor(); + HBasicBlock* succ = block->GetSingleSuccessor(); + // Ensure no computations are merged through throwing block. + // This does not prevent the optimization per se, but would + // require an elaborate clean up of the SSA graph. + if (succ != exit && + !block->Dominates(pred) && + pred->Dominates(succ) && + succ->GetPredecessors().size() > 1u && + succ->GetPhis().IsEmpty()) { + block->ReplaceSuccessor(succ, exit); + rerun_dominance_and_loop_analysis = true; + MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke); + } + } + } + + // We need to re-analyze the graph in order to run DCE afterwards. + if (rerun_dominance_and_loop_analysis) { + graph_->ClearLoopInformation(); + graph_->ClearDominanceInformation(); + graph_->BuildDominatorTree(); + return true; + } + return false; +} + +// Simplify the pattern: +// // B1 B2 ... // goto goto goto // \ | / @@ -381,6 +452,7 @@ void HDeadCodeElimination::Run() { // Simplify graph to generate more dead block patterns. ConnectSuccessiveBlocks(); bool did_any_simplification = false; + did_any_simplification |= SimplifyAlwaysThrows(); did_any_simplification |= SimplifyIfs(); did_any_simplification |= RemoveDeadBlocks(); if (did_any_simplification) { diff --git a/compiler/optimizing/dead_code_elimination.h b/compiler/optimizing/dead_code_elimination.h index 84fd890eee..92a7f562e1 100644 --- a/compiler/optimizing/dead_code_elimination.h +++ b/compiler/optimizing/dead_code_elimination.h @@ -40,6 +40,7 @@ class HDeadCodeElimination : public HOptimization { void MaybeRecordSimplifyIf(); bool RemoveDeadBlocks(); void RemoveDeadInstructions(); + bool SimplifyAlwaysThrows(); bool SimplifyIfs(); void ConnectSuccessiveBlocks(); diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc index b1ac027a68..c88baa8610 100644 --- a/compiler/optimizing/graph_checker.cc +++ b/compiler/optimizing/graph_checker.cc @@ -31,7 +31,15 @@ namespace art { using android::base::StringPrintf; static bool IsAllowedToJumpToExitBlock(HInstruction* instruction) { - return instruction->IsThrow() || instruction->IsReturn() || instruction->IsReturnVoid(); + // Anything that returns is allowed to jump into the exit block. + if (instruction->IsReturn() || instruction->IsReturnVoid()) { + return true; + } + // Anything that always throws is allowed to jump into the exit block. + if (instruction->IsGoto() && instruction->GetPrevious() != nullptr) { + instruction = instruction->GetPrevious(); + } + return instruction->AlwaysThrows(); } static bool IsExitTryBoundaryIntoExitBlock(HBasicBlock* block) { diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 81a75584a4..452be6feae 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -392,6 +392,34 @@ ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { return single_impl; } +static bool AlwaysThrows(ArtMethod* method) { + CodeItemDataAccessor accessor(method); + // Skip native methods, methods with try blocks, and methods that are too large. + if (!accessor.HasCodeItem() || + accessor.TriesSize() != 0 || + accessor.InsnsSizeInCodeUnits() > kMaximumNumberOfTotalInstructions) { + return false; + } + // Scan for exits. + bool throw_seen = false; + for (const DexInstructionPcPair& pair : accessor) { + switch (pair.Inst().Opcode()) { + case Instruction::RETURN: + case Instruction::RETURN_VOID: + case Instruction::RETURN_WIDE: + case Instruction::RETURN_OBJECT: + case Instruction::RETURN_VOID_NO_BARRIER: + return false; // found regular control flow back + case Instruction::THROW: + throw_seen = true; + break; + default: + break; + } + } + return throw_seen; +} + bool HInliner::TryInline(HInvoke* invoke_instruction) { if (invoke_instruction->IsInvokeUnresolved() || invoke_instruction->IsInvokePolymorphic()) { @@ -431,20 +459,29 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { } if (actual_method != nullptr) { + // Single target. bool result = TryInlineAndReplace(invoke_instruction, actual_method, ReferenceTypeInfo::CreateInvalid(), /* do_rtp */ true, cha_devirtualize); - if (result && !invoke_instruction->IsInvokeStaticOrDirect()) { - if (cha_devirtualize) { - // Add dependency due to devirtulization. We've assumed resolved_method - // has single implementation. - outermost_graph_->AddCHASingleImplementationDependency(resolved_method); - MaybeRecordStat(stats_, MethodCompilationStat::kCHAInline); - } else { - MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface); + if (result) { + // Successfully inlined. + if (!invoke_instruction->IsInvokeStaticOrDirect()) { + if (cha_devirtualize) { + // Add dependency due to devirtualization. We've assumed resolved_method + // has single implementation. + outermost_graph_->AddCHASingleImplementationDependency(resolved_method); + MaybeRecordStat(stats_, MethodCompilationStat::kCHAInline); + } else { + MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface); + } } + } else if (!cha_devirtualize && AlwaysThrows(actual_method)) { + // Set always throws property for non-inlined method call with single target + // (unless it was obtained through CHA, because that would imply we have + // to add the CHA dependency, which seems not worth it). + invoke_instruction->SetAlwaysThrows(true); } return result; } diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index d4382c6b4c..2047954207 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -2018,6 +2018,10 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { // TODO: We should rename to CanVisiblyThrow, as some instructions (like HNewInstance), // could throw OOME, but it is still OK to remove them if they are unused. virtual bool CanThrow() const { return false; } + + // Does the instruction always throw an exception unconditionally? + virtual bool AlwaysThrows() const { return false; } + bool CanThrowIntoCatchBlock() const { return CanThrow() && block_->IsTryBlock(); } bool HasSideEffects() const { return side_effects_.HasSideEffects(); } @@ -4169,6 +4173,10 @@ class HInvoke : public HVariableInputSizeInstruction { bool CanThrow() const OVERRIDE { return GetPackedFlag<kFlagCanThrow>(); } + void SetAlwaysThrows(bool always_throws) { SetPackedFlag<kFlagAlwaysThrows>(always_throws); } + + bool AlwaysThrows() const OVERRIDE { return GetPackedFlag<kFlagAlwaysThrows>(); } + bool CanBeMoved() const OVERRIDE { return IsIntrinsic() && !DoesAnyWrite(); } bool InstructionDataEquals(const HInstruction* other) const OVERRIDE { @@ -4199,7 +4207,8 @@ class HInvoke : public HVariableInputSizeInstruction { static constexpr size_t kFieldReturnTypeSize = MinimumBitsToStore(static_cast<size_t>(DataType::Type::kLast)); static constexpr size_t kFlagCanThrow = kFieldReturnType + kFieldReturnTypeSize; - static constexpr size_t kNumberOfInvokePackedBits = kFlagCanThrow + 1; + static constexpr size_t kFlagAlwaysThrows = kFlagCanThrow + 1; + static constexpr size_t kNumberOfInvokePackedBits = kFlagAlwaysThrows + 1; static_assert(kNumberOfInvokePackedBits <= kMaxNumberOfPackedBits, "Too many packed fields."); using InvokeTypeField = BitField<InvokeType, kFieldInvokeType, kFieldInvokeTypeSize>; using ReturnTypeField = BitField<DataType::Type, kFieldReturnType, kFieldReturnTypeSize>; @@ -6575,6 +6584,8 @@ class HThrow FINAL : public HTemplateInstruction<1> { bool CanThrow() const OVERRIDE { return true; } + bool AlwaysThrows() const OVERRIDE { return true; } + DECLARE_INSTRUCTION(Throw); protected: diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h index 32a94ab5e4..0023265e50 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -75,6 +75,7 @@ enum class MethodCompilationStat { kImplicitNullCheckGenerated, kExplicitNullCheckGenerated, kSimplifyIf, + kSimplifyThrowingInvoke, kInstructionSunk, kNotInlinedUnresolvedEntrypoint, kNotInlinedDexCache, diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index 31b6ac305c..44493283db 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -3362,9 +3362,10 @@ bool OatWriter::WriteDexFile(OutputStream* out, return false; } // update_input_vdex disables compact dex and layout. - if (!update_input_vdex && (profile_compilation_info_ != nullptr || - compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone)) { - CHECK(!update_input_vdex) << "We should never update the input vdex when doing dexlayout"; + if (profile_compilation_info_ != nullptr || + compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone) { + CHECK(!update_input_vdex) + << "We should never update the input vdex when doing dexlayout or compact dex"; if (!LayoutAndWriteDexFile(out, oat_dex_file)) { return false; } @@ -3433,6 +3434,11 @@ bool OatWriter::SeekToDexFile(OutputStream* out, File* file, OatDexFile* oat_dex } bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_file) { + // Open dex files and write them into `out`. + // Note that we only verify dex files which do not belong to the boot class path. + // This is because those have been processed by `hiddenapi` and would not pass + // some of the checks. No guarantees are lost, however, as `hiddenapi` verifies + // the dex files prior to processing. TimingLogger::ScopedTiming split("Dex Layout", timings_); std::string error_msg; std::string location(oat_dex_file->GetLocation()); @@ -3449,7 +3455,7 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil dex_file = dex_file_loader.Open(location, zip_entry->GetCrc32(), std::move(mem_map), - /* verify */ true, + /* verify */ !compiling_boot_image_, /* verify_checksum */ true, &error_msg); } else if (oat_dex_file->source_.IsRawFile()) { @@ -3459,8 +3465,11 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil PLOG(ERROR) << "Failed to dup dex file descriptor (" << raw_file->Fd() << ") at " << location; return false; } - dex_file = dex_file_loader.OpenDex( - dup_fd, location, /* verify */ true, /* verify_checksum */ true, &error_msg); + dex_file = dex_file_loader.OpenDex(dup_fd, location, + /* verify */ !compiling_boot_image_, + /* verify_checksum */ true, + /* mmap_shared */ false, + &error_msg); } else { // The source data is a vdex file. CHECK(oat_dex_file->source_.IsRawData()) diff --git a/dexlayout/compact_dex_writer.cc b/dexlayout/compact_dex_writer.cc index d1dc6587c0..dd1eee7c59 100644 --- a/dexlayout/compact_dex_writer.cc +++ b/dexlayout/compact_dex_writer.cc @@ -24,6 +24,18 @@ 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); +} + uint32_t CompactDexWriter::WriteDebugInfoOffsetTable(uint32_t offset) { const uint32_t start_offset = offset; const dex_ir::Collections& collections = header_->GetCollections(); @@ -94,16 +106,50 @@ uint32_t CompactDexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, uint32_t offset, bool reserve_only) { DCHECK(code_item != nullptr); + DCHECK(!reserve_only) << "Not supported because of deduping."; const uint32_t start_offset = offset; + + // Align to minimum requirements, additional alignment requirements are handled below after we + // know the preheader size. offset = RoundUp(offset, CompactDexFile::CodeItem::kAlignment); - ProcessOffset(&offset, code_item); CompactDexFile::CodeItem disk_code_item; - disk_code_item.registers_size_ = code_item->RegistersSize(); - disk_code_item.ins_size_ = code_item->InsSize(); - disk_code_item.outs_size_ = code_item->OutsSize(); - disk_code_item.tries_size_ = code_item->TriesSize(); - disk_code_item.insns_size_in_code_units_ = code_item->InsnsSize(); + + uint16_t preheader_storage[CompactDexFile::CodeItem::kMaxPreHeaderSize] = {}; + uint16_t* preheader_end = preheader_storage + CompactDexFile::CodeItem::kMaxPreHeaderSize; + const uint16_t* preheader = disk_code_item.Create( + code_item->RegistersSize(), + code_item->InsSize(), + code_item->OutsSize(), + code_item->TriesSize(), + code_item->InsnsSize(), + preheader_end); + 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; + 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. + for (const DexInstructionPcPair& instruction : code_item->Instructions()) { + const Instruction::Code opcode = instruction->Opcode(); + // Payload instructions possibly require special alignment for their data. + 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; + break; + } + } + } + + const uint32_t data_start = offset; + + // Write preheader first. + offset += Write(reinterpret_cast<const uint8_t*>(preheader), preheader_bytes, offset); + // Registered offset is after the preheader. + ProcessOffset(&offset, 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, @@ -113,9 +159,32 @@ uint32_t CompactDexWriter::WriteCodeItem(dex_ir::CodeItem* code_item, offset += Write(code_item->Insns(), code_item->InsnsSize() * sizeof(uint16_t), offset); // Write the post instruction data. offset += WriteCodeItemPostInstructionData(code_item, offset, 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) { + code_item->SetOffset(deduped_offset); + // Undo the offset for all that we wrote since we deduped. + offset = start_offset; + } + } + return offset - start_offset; } +uint32_t CompactDexWriter::DedupeData(uint32_t data_start, + uint32_t data_end, + uint32_t item_offset) { + HashedMemoryRange range {data_start, data_end - data_start}; + auto existing = data_dedupe_.emplace(range, item_offset); + if (!existing.second) { + // Failed to insert, item already existed in the map. + return existing.first->second; + } + return kDidNotDedupe; +} + void CompactDexWriter::SortDebugInfosByMethodIndex() { dex_ir::Collections& collections = header_->GetCollections(); static constexpr InvokeType invoke_types[] = { diff --git a/dexlayout/compact_dex_writer.h b/dexlayout/compact_dex_writer.h index 37f6ff11a0..cb53caebc6 100644 --- a/dexlayout/compact_dex_writer.h +++ b/dexlayout/compact_dex_writer.h @@ -19,18 +19,45 @@ #ifndef ART_DEXLAYOUT_COMPACT_DEX_WRITER_H_ #define ART_DEXLAYOUT_COMPACT_DEX_WRITER_H_ +#include <unordered_map> + #include "dex_writer.h" +#include "utils.h" namespace art { +class HashedMemoryRange { + public: + uint32_t offset_; + uint32_t length_; + + class HashEqual { + public: + explicit HashEqual(const uint8_t* data) : data_(data) {} + + // 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_); + } + + // Hash function. + size_t operator()(const HashedMemoryRange& range) const { + return HashBytes(data_ + range.offset_, range.length_); + } + + private: + const uint8_t* data_; + }; +}; + class CompactDexWriter : public DexWriter { public: 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) {} + CompactDexLevel compact_dex_level); protected: void WriteMemMap() OVERRIDE; @@ -41,13 +68,20 @@ class CompactDexWriter : public DexWriter { uint32_t WriteDebugInfoOffsetTable(uint32_t offset); - const CompactDexLevel compact_dex_level_; - uint32_t WriteCodeItem(dex_ir::CodeItem* code_item, uint32_t offset, 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); + 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; @@ -57,6 +91,12 @@ 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_; + DISALLOW_COPY_AND_ASSIGN(CompactDexWriter); }; diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc index 0a59cc9ba2..fb7dff63b7 100644 --- a/dexlayout/dex_ir.cc +++ b/dexlayout/dex_ir.cc @@ -565,18 +565,23 @@ ParameterAnnotation* Collections::GenerateParameterAnnotation( return new ParameterAnnotation(method_id, set_ref_list); } -CodeItem* Collections::CreateCodeItem(const DexFile& dex_file, - const DexFile::CodeItem& disk_code_item, - uint32_t offset, - uint32_t dex_method_index) { - CodeItemDebugInfoAccessor accessor(dex_file, &disk_code_item, dex_method_index); - const uint16_t registers_size = accessor.RegistersSize(); - const uint16_t ins_size = accessor.InsSize(); - const uint16_t outs_size = accessor.OutsSize(); - const uint32_t tries_size = accessor.TriesSize(); - - // TODO: Calculate the size of the debug info. +CodeItem* Collections::DedupeOrCreateCodeItem(const DexFile& dex_file, + const DexFile::CodeItem* disk_code_item, + uint32_t offset, + uint32_t dex_method_index) { + if (disk_code_item == nullptr) { + return nullptr; + } + CodeItemDebugInfoAccessor accessor(dex_file, disk_code_item, dex_method_index); const uint32_t debug_info_offset = accessor.DebugInfoOffset(); + + // Create the offsets pair and dedupe based on it. + std::pair<uint32_t, uint32_t> offsets_pair(offset, debug_info_offset); + auto existing = code_items_map_.find(offsets_pair); + if (existing != code_items_map_.end()) { + return existing->second; + } + const uint8_t* debug_info_stream = dex_file.GetDebugInfoStream(debug_info_offset); DebugInfoItem* debug_info = nullptr; if (debug_info_stream != nullptr) { @@ -596,7 +601,7 @@ CodeItem* Collections::CreateCodeItem(const DexFile& dex_file, TryItemVector* tries = nullptr; CatchHandlerVector* handler_list = nullptr; - if (tries_size > 0) { + if (accessor.TriesSize() > 0) { tries = new TryItemVector(); handler_list = new CatchHandlerVector(); for (const DexFile::TryItem& disk_try_item : accessor.TryItems()) { @@ -671,11 +676,25 @@ CodeItem* Collections::CreateCodeItem(const DexFile& dex_file, } } - uint32_t size = dex_file.GetCodeItemSize(disk_code_item); - CodeItem* code_item = new CodeItem( - registers_size, ins_size, outs_size, debug_info, insns_size, insns, tries, handler_list); + uint32_t size = dex_file.GetCodeItemSize(*disk_code_item); + CodeItem* code_item = new CodeItem(accessor.RegistersSize(), + accessor.InsSize(), + accessor.OutsSize(), + debug_info, + insns_size, + insns, + tries, + handler_list); code_item->SetSize(size); - AddItem(code_items_map_, code_items_, code_item, offset); + + // Add the code item to the map. + DCHECK(!code_item->OffsetAssigned()); + if (eagerly_assign_offsets_) { + code_item->SetOffset(offset); + } + code_items_map_.emplace(offsets_pair, code_item); + code_items_.AddItem(code_item); + // Add "fixup" references to types, strings, methods, and fields. // This is temporary, as we will probably want more detailed parsing of the // instructions here. @@ -703,17 +722,12 @@ MethodItem* Collections::GenerateMethodItem(const DexFile& dex_file, ClassDataIt MethodId* method_id = GetMethodId(cdii.GetMemberIndex()); uint32_t access_flags = cdii.GetRawMemberAccessFlags(); const DexFile::CodeItem* disk_code_item = cdii.GetMethodCodeItem(); - CodeItem* code_item = code_items_map_.GetExistingObject(cdii.GetMethodCodeItemOffset()); - DebugInfoItem* debug_info = nullptr; - if (disk_code_item != nullptr) { - if (code_item == nullptr) { - code_item = CreateCodeItem(dex_file, - *disk_code_item, - cdii.GetMethodCodeItemOffset(), - cdii.GetMemberIndex()); - } - debug_info = code_item->DebugInfo(); - } + // Temporary hack to prevent incorrectly deduping code items if they have the same offset since + // they may have different debug info streams. + CodeItem* code_item = DedupeOrCreateCodeItem(dex_file, + disk_code_item, + cdii.GetMethodCodeItemOffset(), + cdii.GetMemberIndex()); return new MethodItem(access_flags, method_id, code_item); } @@ -819,16 +833,16 @@ void Collections::CreateMethodHandleItem(const DexFile& dex_file, uint32_t i) { } void Collections::SortVectorsByMapOrder() { - string_datas_map_.SortVectorByMapOrder(string_datas_); - type_lists_map_.SortVectorByMapOrder(type_lists_); - encoded_array_items_map_.SortVectorByMapOrder(encoded_array_items_); - annotation_items_map_.SortVectorByMapOrder(annotation_items_); - annotation_set_items_map_.SortVectorByMapOrder(annotation_set_items_); - annotation_set_ref_lists_map_.SortVectorByMapOrder(annotation_set_ref_lists_); - annotations_directory_items_map_.SortVectorByMapOrder(annotations_directory_items_); - debug_info_items_map_.SortVectorByMapOrder(debug_info_items_); - code_items_map_.SortVectorByMapOrder(code_items_); - class_datas_map_.SortVectorByMapOrder(class_datas_); + string_datas_.SortByMapOrder(string_datas_map_.Collection()); + type_lists_.SortByMapOrder(type_lists_map_.Collection()); + encoded_array_items_.SortByMapOrder(encoded_array_items_map_.Collection()); + annotation_items_.SortByMapOrder(annotation_items_map_.Collection()); + annotation_set_items_.SortByMapOrder(annotation_set_items_map_.Collection()); + annotation_set_ref_lists_.SortByMapOrder(annotation_set_ref_lists_map_.Collection()); + annotations_directory_items_.SortByMapOrder(annotations_directory_items_map_.Collection()); + debug_info_items_.SortByMapOrder(debug_info_items_map_.Collection()); + code_items_.SortByMapOrder(code_items_map_); + class_datas_.SortByMapOrder(class_datas_map_.Collection()); } static uint32_t HeaderOffset(const dex_ir::Collections& collections ATTRIBUTE_UNUSED) { diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h index ca47b348f1..3627717abe 100644 --- a/dexlayout/dex_ir.h +++ b/dexlayout/dex_ir.h @@ -135,6 +135,20 @@ template<class T> class CollectionVector : public CollectionBase<T> { Vector& Collection() { return collection_; } const Vector& Collection() const { return collection_; } + // Sort the vector by copying pointers over. + template <typename MapType> + void SortByMapOrder(const MapType& map) { + auto it = map.begin(); + CHECK_EQ(map.size(), Size()); + for (size_t i = 0; i < Size(); ++i) { + // There are times when the array will temporarily contain the same pointer twice, doing the + // release here sure there is no double free errors. + Collection()[i].release(); + Collection()[i].reset(it->second); + ++it; + } + } + protected: Vector collection_; @@ -172,22 +186,10 @@ template<class T> class CollectionMap : public CollectionBase<T> { return it != collection_.end() ? it->second : nullptr; } - uint32_t Size() const { return collection_.size(); } + // Lower case for template interop with std::map. + uint32_t size() const { return collection_.size(); } std::map<uint32_t, T*>& Collection() { return collection_; } - // Sort the vector by copying pointers over. - void SortVectorByMapOrder(CollectionVector<T>& vector) { - auto it = collection_.begin(); - CHECK_EQ(vector.Size(), Size()); - for (size_t i = 0; i < Size(); ++i) { - // There are times when the array will temporarily contain the same pointer twice, doing the - // release here sure there is no double free errors. - vector.Collection()[i].release(); - vector.Collection()[i].reset(it->second); - ++it; - } - } - private: std::map<uint32_t, T*> collection_; @@ -254,10 +256,10 @@ class Collections { const DexFile::AnnotationSetItem* disk_annotations_item, uint32_t offset); AnnotationsDirectoryItem* CreateAnnotationsDirectoryItem(const DexFile& dex_file, const DexFile::AnnotationsDirectoryItem* disk_annotations_item, uint32_t offset); - CodeItem* CreateCodeItem(const DexFile& dex_file, - const DexFile::CodeItem& disk_code_item, - uint32_t offset, - uint32_t dex_method_index); + CodeItem* DedupeOrCreateCodeItem(const DexFile& dex_file, + const DexFile::CodeItem* disk_code_item, + uint32_t offset, + uint32_t dex_method_index); ClassData* CreateClassData(const DexFile& dex_file, const uint8_t* encoded_data, uint32_t offset); void AddAnnotationsFromMapListSection(const DexFile& dex_file, uint32_t start_offset, @@ -460,7 +462,10 @@ class Collections { CollectionMap<AnnotationSetRefList> annotation_set_ref_lists_map_; CollectionMap<AnnotationsDirectoryItem> annotations_directory_items_map_; CollectionMap<DebugInfoItem> debug_info_items_map_; - CollectionMap<CodeItem> code_items_map_; + // Code item maps need to check both the debug info offset and debug info offset, do not use + // CollectionMap. + // First offset is the code item offset, second is the debug info offset. + std::map<std::pair<uint32_t, uint32_t>, CodeItem*> code_items_map_; CollectionMap<ClassData> class_datas_map_; uint32_t map_list_offset_ = 0; diff --git a/dexlayout/dex_writer.cc b/dexlayout/dex_writer.cc index 6e1cb62f0b..d26c9481b4 100644 --- a/dexlayout/dex_writer.cc +++ b/dexlayout/dex_writer.cc @@ -512,8 +512,8 @@ uint32_t DexWriter::WriteCodeItemPostInstructionData(dex_ir::CodeItem* code_item bool reserve_only) { const uint32_t start_offset = offset; if (code_item->TriesSize() != 0) { - // Make sure the try items are properly aligned. - offset = RoundUp(offset, kDexTryItemAlignment); + // Align for the try items. + offset = RoundUp(offset, DexFile::TryItem::kAlignment); // Write try items. for (std::unique_ptr<const dex_ir::TryItem>& try_item : *code_item->Tries()) { DexFile::TryItem disk_try_item; diff --git a/dexlayout/dex_writer.h b/dexlayout/dex_writer.h index fdeb299aa4..892ea7414b 100644 --- a/dexlayout/dex_writer.h +++ b/dexlayout/dex_writer.h @@ -62,7 +62,6 @@ class DexWriter { public: static constexpr uint32_t kDataSectionAlignment = sizeof(uint32_t) * 2; static constexpr uint32_t kDexSectionWordAlignment = 4; - static constexpr uint32_t kDexTryItemAlignment = sizeof(uint32_t); static inline constexpr uint32_t SectionAlignment(DexFile::MapItemType type) { switch (type) { diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index a43dd074f8..3d3b121190 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1820,8 +1820,13 @@ void DexLayout::OutputDexFile(const DexFile* dex_file, bool compute_offsets) { // 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. - constexpr size_t kReserveFraction = 10; - const size_t max_size = header_->FileSize() + header_->FileSize() / kReserveFraction; + 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_) { std::string output_location(options_.output_dex_directory_); size_t last_slash = dex_file_location.rfind('/'); @@ -1924,6 +1929,8 @@ void DexLayout::ProcessDexFile(const char* file_name, if (options_.verify_output_) { 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; std::unique_ptr<const DexFile> output_dex_file( dex_file_loader.Open(mem_map_->Begin(), @@ -1931,7 +1938,7 @@ void DexLayout::ProcessDexFile(const char* file_name, location, /* checksum */ 0, /*oat_dex_file*/ nullptr, - /*verify*/ true, + verify, /*verify_checksum*/ false, &error_msg)); CHECK(output_dex_file != nullptr) << "Failed to re-open output file:" << error_msg; diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h index 25afb773bd..cb0eabc7db 100644 --- a/dexlayout/dexlayout.h +++ b/dexlayout/dexlayout.h @@ -65,6 +65,8 @@ class Options { bool visualize_pattern_ = false; bool update_checksum_ = false; CompactDexLevel compact_dex_level_ = CompactDexLevel::kCompactDexLevelNone; + // Disabled until dex2oat properly handles quickening of deduped code items. + bool dedupe_code_items_ = false; OutputFormat output_format_ = kOutputPlain; const char* output_dex_directory_ = nullptr; const char* output_file_name_ = nullptr; diff --git a/runtime/Android.bp b/runtime/Android.bp index 2e34bafd54..e30a06c0a5 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -40,9 +40,6 @@ cc_defaults { target: { android: { - shared_libs: [ - "libutils", - ], static_libs: [ "libz", "libbase", @@ -54,6 +51,9 @@ 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"], @@ -78,6 +78,11 @@ cc_defaults { art_cc_library { name: "libdexfile", defaults: ["libdexfile_defaults"], + vendor_available: true, + vndk: { + enabled: true, + support_system_process: true, + }, // Leave the symbols in the shared library so that stack unwinders can // produce meaningful name resolution. strip: { diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index c09baea72a..737d2a86a1 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -794,27 +794,24 @@ END art_quick_unlock_object_no_inline .extern artInstanceOfFromCode .extern artThrowClassCastExceptionForObject ENTRY art_quick_check_instance_of - push {r0-r1, lr} @ save arguments, link register and pad - .cfi_adjust_cfa_offset 12 + push {r0-r2, lr} @ save arguments, padding (r2) and link register + .cfi_adjust_cfa_offset 16 .cfi_rel_offset r0, 0 .cfi_rel_offset r1, 4 - .cfi_rel_offset lr, 8 - sub sp, #4 - .cfi_adjust_cfa_offset 4 + .cfi_rel_offset r2, 8 + .cfi_rel_offset lr, 12 bl artInstanceOfFromCode cbz r0, .Lthrow_class_cast_exception - add sp, #4 - .cfi_adjust_cfa_offset -4 - pop {r0-r1, pc} - .cfi_adjust_cfa_offset 4 @ Reset unwind info so following code unwinds. + pop {r0-r2, pc} + .Lthrow_class_cast_exception: - add sp, #4 - .cfi_adjust_cfa_offset -4 - pop {r0-r1, lr} - .cfi_adjust_cfa_offset -12 + pop {r0-r2, lr} + .cfi_adjust_cfa_offset -16 .cfi_restore r0 .cfi_restore r1 + .cfi_restore r2 .cfi_restore lr + SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r2 @ save all registers as basis for long jump context mov r2, r9 @ pass Thread::Current bl artThrowClassCastExceptionForObject @ (Object*, Class*, Thread*) diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 96a1cadab9..b0e7b0a964 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1341,12 +1341,14 @@ ENTRY art_quick_check_instance_of // Call runtime code bl artInstanceOfFromCode + // Restore LR. + RESTORE_REG xLR, 24 + // Check for exception cbz x0, .Lthrow_class_cast_exception // Restore and return .cfi_remember_state - RESTORE_REG xLR, 24 RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32 ret .cfi_restore_state // Reset unwind info so following code unwinds. @@ -1354,7 +1356,6 @@ ENTRY art_quick_check_instance_of .Lthrow_class_cast_exception: // Restore - RESTORE_REG xLR, 24 RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32 SETUP_SAVE_ALL_CALLEE_SAVES_FRAME // save all registers as basis for long jump context diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 93cb6656dc..5a28120b30 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -1436,17 +1436,18 @@ DEFINE_FUNCTION art_quick_check_instance_of PUSH eax // pass arg1 - obj call SYMBOL(artInstanceOfFromCode) // (Object* obj, Class* ref_klass) testl %eax, %eax - jz 1f // jump forward if not assignable + jz .Lthrow_class_cast_exception // jump forward if not assignable addl LITERAL(12), %esp // pop arguments CFI_ADJUST_CFA_OFFSET(-12) ret - CFI_ADJUST_CFA_OFFSET(12) // Reset unwind info so following code unwinds. -1: + +.Lthrow_class_cast_exception: POP eax // pop arguments POP ecx addl LITERAL(4), %esp CFI_ADJUST_CFA_OFFSET(-4) + SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx, ebx // save all registers as basis for long jump context // Outgoing argument set up PUSH eax // alignment padding diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 85f972309b..781ade99ce 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -1410,21 +1410,21 @@ DEFINE_FUNCTION art_quick_check_instance_of SETUP_FP_CALLEE_SAVE_FRAME call SYMBOL(artInstanceOfFromCode) // (Object* obj, Class* ref_klass) testq %rax, %rax - jz 1f // jump forward if not assignable + jz .Lthrow_class_cast_exception // jump forward if not assignable + CFI_REMEMBER_STATE RESTORE_FP_CALLEE_SAVE_FRAME addq LITERAL(24), %rsp // pop arguments CFI_ADJUST_CFA_OFFSET(-24) - -.Lreturn: ret + CFI_RESTORE_STATE // Reset unwind info so following code unwinds. - CFI_ADJUST_CFA_OFFSET(24 + 4 * 8) // Reset unwind info so following code unwinds. -1: +.Lthrow_class_cast_exception: RESTORE_FP_CALLEE_SAVE_FRAME addq LITERAL(8), %rsp // pop padding CFI_ADJUST_CFA_OFFSET(-8) POP rsi // Pop arguments POP rdi + SETUP_SAVE_ALL_CALLEE_SAVES_FRAME // save all registers as basis for long jump context mov %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current() call SYMBOL(artThrowClassCastExceptionForObject) // (Object* src, Class* dest, Thread*) diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc index bc726354a8..4689ae4c3f 100644 --- a/runtime/class_loader_context_test.cc +++ b/runtime/class_loader_context_test.cc @@ -278,14 +278,17 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFiles) { VerifyOpenDexFiles(context.get(), 1, &all_dex_files1); } -static std::string CreateRelativeString(const std::string& in, const char* cwd) { +// Creates a relative path from cwd to 'in'. Returns false if it cannot be done. +// TODO We should somehow support this in all situations. b/72042237. +static bool CreateRelativeString(const std::string& in, const char* cwd, std::string* out) { int cwd_len = strlen(cwd); if (!android::base::StartsWith(in, cwd) || (cwd_len < 1)) { - LOG(FATAL) << in << " " << cwd; + return false; } bool contains_trailing_slash = (cwd[cwd_len - 1] == '/'); int start_position = cwd_len + (contains_trailing_slash ? 0 : 1); - return in.substr(start_position); + *out = in.substr(start_position); + return true; } TEST_F(ClassLoaderContextTest, OpenValidDexFilesRelative) { @@ -293,9 +296,17 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesRelative) { if (getcwd(cwd_buf, arraysize(cwd_buf)) == nullptr) { PLOG(FATAL) << "Could not get working directory"; } - std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf); - std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf); - std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf); + std::string multidex_name; + std::string myclass_dex_name; + std::string dex_name; + if (!CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf, &multidex_name) || + !CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf, &myclass_dex_name) || + !CreateRelativeString(GetTestDexFileName("Main"), cwd_buf, &dex_name)) { + LOG(ERROR) << "Test OpenValidDexFilesRelative cannot be run because target dex files have no " + << "relative path."; + SUCCEED(); + return; + } std::unique_ptr<ClassLoaderContext> context = @@ -321,10 +332,17 @@ TEST_F(ClassLoaderContextTest, OpenValidDexFilesClasspathDir) { if (getcwd(cwd_buf, arraysize(cwd_buf)) == nullptr) { PLOG(FATAL) << "Could not get working directory"; } - std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf); - std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf); - std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf); - + std::string multidex_name; + std::string myclass_dex_name; + std::string dex_name; + if (!CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf, &multidex_name) || + !CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf, &myclass_dex_name) || + !CreateRelativeString(GetTestDexFileName("Main"), cwd_buf, &dex_name)) { + LOG(ERROR) << "Test OpenValidDexFilesClasspathDir cannot be run because target dex files have " + << "no relative path."; + SUCCEED(); + return; + } std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create( "PCL[" + multidex_name + ":" + myclass_dex_name + "];" + diff --git a/runtime/dex/art_dex_file_loader.cc b/runtime/dex/art_dex_file_loader.cc index 282b282707..dee736ecff 100644 --- a/runtime/dex/art_dex_file_loader.cc +++ b/runtime/dex/art_dex_file_loader.cc @@ -127,8 +127,12 @@ bool ArtDexFileLoader::GetMultiDexChecksums(const char* filename, return true; } if (IsMagicValid(magic)) { - std::unique_ptr<const DexFile> dex_file( - OpenFile(fd.Release(), filename, false, false, error_msg)); + std::unique_ptr<const DexFile> dex_file(OpenFile(fd.Release(), + filename, + /* verify */ false, + /* verify_checksum */ false, + /* mmap_shared */ false, + error_msg)); if (dex_file == nullptr) { return false; } @@ -211,6 +215,7 @@ bool ArtDexFileLoader::Open(const char* filename, location, verify, verify_checksum, + /* mmap_shared */ false, error_msg)); if (dex_file.get() != nullptr) { dex_files->push_back(std::move(dex_file)); @@ -227,9 +232,10 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenDex(int fd, const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const { ScopedTrace trace("Open dex file " + std::string(location)); - return OpenFile(fd, location, verify, verify_checksum, error_msg); + return OpenFile(fd, location, verify, verify_checksum, mmap_shared, error_msg); } bool ArtDexFileLoader::OpenZip(int fd, @@ -253,6 +259,7 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenFile(int fd, const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const { ScopedTrace trace(std::string("Open dex file ") + std::string(location)); CHECK(!location.empty()); @@ -273,7 +280,7 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenFile(int fd, size_t length = sbuf.st_size; map.reset(MemMap::MapFile(length, PROT_READ, - MAP_PRIVATE, + mmap_shared ? MAP_SHARED : MAP_PRIVATE, fd, 0, /*low_4gb*/false, diff --git a/runtime/dex/art_dex_file_loader.h b/runtime/dex/art_dex_file_loader.h index a6191d9f54..8c12bf3137 100644 --- a/runtime/dex/art_dex_file_loader.h +++ b/runtime/dex/art_dex_file_loader.h @@ -83,6 +83,7 @@ class ArtDexFileLoader : public DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const OVERRIDE; // Opens dex files from within a .jar, .zip, or .apk file @@ -98,6 +99,7 @@ class ArtDexFileLoader : public DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const OVERRIDE; // Open all classesXXX.dex files from a zip archive. diff --git a/runtime/dex/code_item_accessors-no_art-inl.h b/runtime/dex/code_item_accessors-no_art-inl.h index aaa86d4b14..6a99009b00 100644 --- a/runtime/dex/code_item_accessors-no_art-inl.h +++ b/runtime/dex/code_item_accessors-no_art-inl.h @@ -26,14 +26,25 @@ // The no ART version is used by binaries that don't include the whole runtime. namespace art { +inline void CodeItemInstructionAccessor::Init(uint32_t insns_size_in_code_units, + const uint16_t* insns) { + insns_size_in_code_units_ = insns_size_in_code_units; + insns_ = insns; +} + inline void CodeItemInstructionAccessor::Init(const CompactDexFile::CodeItem& code_item) { - insns_size_in_code_units_ = code_item.insns_size_in_code_units_; - insns_ = code_item.insns_; + uint32_t insns_size_in_code_units; + code_item.DecodeFields</*kDecodeOnlyInstructionCount*/ true>( + &insns_size_in_code_units, + /*registers_size*/ nullptr, + /*ins_size*/ nullptr, + /*outs_size*/ nullptr, + /*tries_size*/ nullptr); + Init(insns_size_in_code_units, code_item.insns_); } inline void CodeItemInstructionAccessor::Init(const StandardDexFile::CodeItem& code_item) { - insns_size_in_code_units_ = code_item.insns_size_in_code_units_; - insns_ = code_item.insns_; + Init(code_item.insns_size_in_code_units_, code_item.insns_); } inline void CodeItemInstructionAccessor::Init(const DexFile& dex_file, @@ -72,11 +83,13 @@ inline IterationRange<DexInstructionIterator> CodeItemInstructionAccessor::Instr } inline void CodeItemDataAccessor::Init(const CompactDexFile::CodeItem& code_item) { - CodeItemInstructionAccessor::Init(code_item); - registers_size_ = code_item.registers_size_; - ins_size_ = code_item.ins_size_; - outs_size_ = code_item.outs_size_; - tries_size_ = code_item.tries_size_; + uint32_t insns_size_in_code_units; + code_item.DecodeFields</*kDecodeOnlyInstructionCount*/ false>(&insns_size_in_code_units, + ®isters_size_, + &ins_size_, + &outs_size_, + &tries_size_); + CodeItemInstructionAccessor::Init(insns_size_in_code_units, code_item.insns_); } inline void CodeItemDataAccessor::Init(const StandardDexFile::CodeItem& code_item) { diff --git a/runtime/dex/code_item_accessors.h b/runtime/dex/code_item_accessors.h index 66531f96bc..08f823cae8 100644 --- a/runtime/dex/code_item_accessors.h +++ b/runtime/dex/code_item_accessors.h @@ -66,6 +66,7 @@ class CodeItemInstructionAccessor { protected: CodeItemInstructionAccessor() = default; + ALWAYS_INLINE void Init(uint32_t insns_size_in_code_units, const uint16_t* insns); ALWAYS_INLINE void Init(const CompactDexFile::CodeItem& code_item); ALWAYS_INLINE void Init(const StandardDexFile::CodeItem& code_item); ALWAYS_INLINE void Init(const DexFile& dex_file, const DexFile::CodeItem* code_item); diff --git a/runtime/dex/code_item_accessors_test.cc b/runtime/dex/code_item_accessors_test.cc index 2e219562e9..3380be8acf 100644 --- a/runtime/dex/code_item_accessors_test.cc +++ b/runtime/dex/code_item_accessors_test.cc @@ -62,8 +62,8 @@ TEST(CodeItemAccessorsTest, TestDexInstructionsAccessor) { ASSERT_TRUE(standard_dex != nullptr); std::unique_ptr<const DexFile> compact_dex(CreateFakeDex(/*compact_dex*/true)); ASSERT_TRUE(compact_dex != nullptr); - static constexpr uint16_t kRegisterSize = 1; - static constexpr uint16_t kInsSize = 2; + static constexpr uint16_t kRegisterSize = 2; + static constexpr uint16_t kInsSize = 1; static constexpr uint16_t kOutsSize = 3; static constexpr uint16_t kTriesSize = 4; // debug_info_off_ is not accessible from the helpers yet. @@ -97,12 +97,16 @@ TEST(CodeItemAccessorsTest, TestDexInstructionsAccessor) { verify_code_item(standard_dex.get(), dex_code_item, dex_code_item->insns_); CompactDexFile::CodeItem* cdex_code_item = - reinterpret_cast<CompactDexFile::CodeItem*>(const_cast<uint8_t*>(compact_dex->Begin())); - cdex_code_item->registers_size_ = kRegisterSize; - cdex_code_item->ins_size_ = kInsSize; - cdex_code_item->outs_size_ = kOutsSize; - cdex_code_item->tries_size_ = kTriesSize; - cdex_code_item->insns_size_in_code_units_ = kInsnsSizeInCodeUnits; + reinterpret_cast<CompactDexFile::CodeItem*>(const_cast<uint8_t*>(compact_dex->Begin() + + CompactDexFile::CodeItem::kMaxPreHeaderSize * sizeof(uint16_t))); + std::vector<uint16_t> preheader; + cdex_code_item->Create(kRegisterSize, + kInsSize, + kOutsSize, + kTriesSize, + kInsnsSizeInCodeUnits, + cdex_code_item->GetPreHeader()); + verify_code_item(compact_dex.get(), cdex_code_item, cdex_code_item->insns_); } diff --git a/runtime/dex/compact_dex_file.h b/runtime/dex/compact_dex_file.h index af782a981a..8dad84d5cd 100644 --- a/runtime/dex/compact_dex_file.h +++ b/runtime/dex/compact_dex_file.h @@ -55,27 +55,162 @@ class CompactDexFile : public DexFile { friend class CompactDexWriter; }; - // Like the standard code item except without a debug info offset. + // Like the standard code item except without a debug info offset. Each code item may have a + // preheader to encode large methods. In 99% of cases, the preheader is not used. This enables + // smaller size with a good fast path case in the accessors. struct CodeItem : public DexFile::CodeItem { - static constexpr size_t kAlignment = sizeof(uint32_t); + static constexpr size_t kAlignment = sizeof(uint16_t); + // Max preheader size in uint16_ts. + static constexpr size_t kMaxPreHeaderSize = 6; private: CodeItem() = default; - uint16_t registers_size_; // the number of registers used by this code - // (locals + parameters) - uint16_t ins_size_; // the number of words of incoming arguments to the method - // that this code is for - uint16_t outs_size_; // the number of words of outgoing argument space required - // by this code for method invocation - uint16_t tries_size_; // the number of try_items for this instance. If non-zero, - // then these appear as the tries array just after the - // insns in this instance. - - uint32_t insns_size_in_code_units_; // size of the insns array, in 2 byte code units + static constexpr size_t kRegistersSizeShift = 12; + static constexpr size_t kInsSizeShift = 8; + static constexpr size_t kOutsSizeShift = 4; + static constexpr size_t kTriesSizeSizeShift = 0; + static constexpr uint16_t kFlagPreHeaderRegisterSize = 0x1 << 0; + static constexpr uint16_t kFlagPreHeaderInsSize = 0x1 << 1; + static constexpr uint16_t kFlagPreHeaderOutsSize = 0x1 << 2; + static constexpr uint16_t kFlagPreHeaderTriesSize = 0x1 << 3; + static constexpr uint16_t kFlagPreHeaderInsnsSize = 0x1 << 4; + static constexpr size_t kInsnsSizeShift = 5; + static constexpr size_t kInsnsSizeBits = sizeof(uint16_t) * kBitsPerByte - kInsnsSizeShift; + + // Combined preheader flags for fast testing if we need to go slow path. + static constexpr uint16_t kFlagPreHeaderCombined = + kFlagPreHeaderRegisterSize | + kFlagPreHeaderInsSize | + kFlagPreHeaderOutsSize | + kFlagPreHeaderTriesSize | + kFlagPreHeaderInsnsSize; + + // Create a code item and associated preheader if required based on field values. + // Returns the start of the preheader. The preheader buffer must be at least as large as + // kMaxPreHeaderSize; + uint16_t* Create(uint16_t registers_size, + uint16_t ins_size, + uint16_t outs_size, + uint16_t tries_size, + uint32_t insns_size_in_code_units, + uint16_t* out_preheader) { + // Dex verification ensures that registers size > ins_size, so we can subtract the registers + // size accordingly to reduce how often we need to use the preheader. + DCHECK_GE(registers_size, ins_size); + registers_size -= ins_size; + fields_ = (registers_size & 0xF) << kRegistersSizeShift; + fields_ |= (ins_size & 0xF) << kInsSizeShift; + fields_ |= (outs_size & 0xF) << kOutsSizeShift; + fields_ |= (tries_size & 0xF) << kTriesSizeSizeShift; + registers_size &= ~0xF; + ins_size &= ~0xF; + outs_size &= ~0xF; + tries_size &= ~0xF; + insns_count_and_flags_ = 0; + const size_t masked_count = insns_size_in_code_units & ((1 << kInsnsSizeBits) - 1); + insns_count_and_flags_ |= masked_count << kInsnsSizeShift; + insns_size_in_code_units -= masked_count; + + // Since the preheader case is rare (1% of code items), use a suboptimally large but fast + // decoding format. + if (insns_size_in_code_units != 0) { + insns_count_and_flags_ |= kFlagPreHeaderInsnsSize; + --out_preheader; + *out_preheader = static_cast<uint16_t>(insns_size_in_code_units); + --out_preheader; + *out_preheader = static_cast<uint16_t>(insns_size_in_code_units >> 16); + } + auto preheader_encode = [&](uint16_t size, uint16_t flag) { + if (size != 0) { + insns_count_and_flags_ |= flag; + --out_preheader; + *out_preheader = size; + } + }; + preheader_encode(registers_size, kFlagPreHeaderRegisterSize); + preheader_encode(ins_size, kFlagPreHeaderInsSize); + preheader_encode(outs_size, kFlagPreHeaderOutsSize); + preheader_encode(tries_size, kFlagPreHeaderTriesSize); + return out_preheader; + } + + ALWAYS_INLINE bool HasPreHeader(uint16_t flag) const { + return (insns_count_and_flags_ & flag) != 0; + } + + // Return true if the code item has any preheaders. + ALWAYS_INLINE static bool HasAnyPreHeader(uint16_t insns_count_and_flags) { + return (insns_count_and_flags & kFlagPreHeaderCombined) != 0; + } + + ALWAYS_INLINE uint16_t* GetPreHeader() { + return reinterpret_cast<uint16_t*>(this); + } + + ALWAYS_INLINE const uint16_t* GetPreHeader() const { + return reinterpret_cast<const uint16_t*>(this); + } + + // Decode fields and read the preheader if necessary. If kDecodeOnlyInstructionCount is + // specified then only the instruction count is decoded. + template <bool kDecodeOnlyInstructionCount> + ALWAYS_INLINE void DecodeFields(uint32_t* insns_count, + uint16_t* registers_size, + uint16_t* ins_size, + uint16_t* outs_size, + uint16_t* tries_size) const { + *insns_count = insns_count_and_flags_ >> kInsnsSizeShift; + if (!kDecodeOnlyInstructionCount) { + const uint16_t fields = fields_; + *registers_size = (fields >> kRegistersSizeShift) & 0xF; + *ins_size = (fields >> kInsSizeShift) & 0xF; + *outs_size = (fields >> kOutsSizeShift) & 0xF; + *tries_size = (fields >> kTriesSizeSizeShift) & 0xF; + } + if (UNLIKELY(HasAnyPreHeader(insns_count_and_flags_))) { + const uint16_t* preheader = GetPreHeader(); + if (HasPreHeader(kFlagPreHeaderInsnsSize)) { + --preheader; + *insns_count += static_cast<uint32_t>(*preheader); + --preheader; + *insns_count += static_cast<uint32_t>(*preheader) << 16; + } + if (!kDecodeOnlyInstructionCount) { + if (HasPreHeader(kFlagPreHeaderRegisterSize)) { + --preheader; + *registers_size += preheader[0]; + } + if (HasPreHeader(kFlagPreHeaderInsSize)) { + --preheader; + *ins_size += preheader[0]; + } + if (HasPreHeader(kFlagPreHeaderOutsSize)) { + --preheader; + *outs_size += preheader[0]; + } + if (HasPreHeader(kFlagPreHeaderTriesSize)) { + --preheader; + *tries_size += preheader[0]; + } + } + } + if (!kDecodeOnlyInstructionCount) { + *registers_size += *ins_size; + } + } + + // Packed code item data, 4 bits each: [registers_size, ins_size, outs_size, tries_size] + uint16_t fields_; + + // 5 bits for if either of the fields required preheader extension, 11 bits for the number of + // instruction code units. + uint16_t insns_count_and_flags_; + uint16_t insns_[1]; // actual array of bytecode. ART_FRIEND_TEST(CodeItemAccessorsTest, TestDexInstructionsAccessor); + ART_FRIEND_TEST(CompactDexFileTest, CodeItemFields); friend class CodeItemDataAccessor; friend class CodeItemDebugInfoAccessor; friend class CodeItemInstructionAccessor; diff --git a/runtime/dex/compact_dex_file_test.cc b/runtime/dex/compact_dex_file_test.cc index d665dc994b..517c5873ed 100644 --- a/runtime/dex/compact_dex_file_test.cc +++ b/runtime/dex/compact_dex_file_test.cc @@ -14,15 +14,14 @@ * limitations under the License. */ -#include "common_runtime_test.h" + #include "compact_dex_file.h" #include "dex_file_loader.h" +#include "gtest/gtest.h" namespace art { -class CompactDexFileTest : public CommonRuntimeTest {}; - -TEST_F(CompactDexFileTest, MagicAndVersion) { +TEST(CompactDexFileTest, MagicAndVersion) { // Test permutations of valid/invalid headers. for (size_t i = 0; i < 2; ++i) { for (size_t j = 0; j < 2; ++j) { @@ -45,4 +44,58 @@ TEST_F(CompactDexFileTest, MagicAndVersion) { } } +TEST(CompactDexFileTest, CodeItemFields) { + auto test_and_write = [&] (uint16_t registers_size, + uint16_t ins_size, + uint16_t outs_size, + uint16_t tries_size, + uint32_t insns_size_in_code_units) { + ASSERT_GE(registers_size, ins_size); + uint16_t buffer[sizeof(CompactDexFile::CodeItem) + + CompactDexFile::CodeItem::kMaxPreHeaderSize] = {}; + CompactDexFile::CodeItem* code_item = reinterpret_cast<CompactDexFile::CodeItem*>( + &buffer[CompactDexFile::CodeItem::kMaxPreHeaderSize]); + const uint16_t* preheader_ptr = code_item->Create(registers_size, + ins_size, + outs_size, + tries_size, + insns_size_in_code_units, + code_item->GetPreHeader()); + ASSERT_GT(preheader_ptr, buffer); + + uint16_t out_registers_size; + uint16_t out_ins_size; + uint16_t out_outs_size; + uint16_t out_tries_size; + uint32_t out_insns_size_in_code_units; + code_item->DecodeFields</*kDecodeOnlyInstructionCount*/false>(&out_insns_size_in_code_units, + &out_registers_size, + &out_ins_size, + &out_outs_size, + &out_tries_size); + ASSERT_EQ(registers_size, out_registers_size); + ASSERT_EQ(ins_size, out_ins_size); + ASSERT_EQ(outs_size, out_outs_size); + ASSERT_EQ(tries_size, out_tries_size); + ASSERT_EQ(insns_size_in_code_units, out_insns_size_in_code_units); + + ++out_insns_size_in_code_units; // Force value to change. + code_item->DecodeFields</*kDecodeOnlyInstructionCount*/true>(&out_insns_size_in_code_units, + /*registers_size*/ nullptr, + /*ins_size*/ nullptr, + /*outs_size*/ nullptr, + /*tries_size*/ nullptr); + ASSERT_EQ(insns_size_in_code_units, out_insns_size_in_code_units); + }; + static constexpr uint32_t kMax32 = std::numeric_limits<uint32_t>::max(); + static constexpr uint16_t kMax16 = std::numeric_limits<uint16_t>::max(); + test_and_write(0, 0, 0, 0, 0); + test_and_write(kMax16, kMax16, kMax16, kMax16, kMax32); + test_and_write(kMax16 - 1, kMax16 - 2, kMax16 - 3, kMax16 - 4, kMax32 - 5); + test_and_write(kMax16 - 4, kMax16 - 5, kMax16 - 3, kMax16 - 2, kMax32 - 1); + test_and_write(5, 4, 3, 2, 1); + test_and_write(5, 0, 3, 2, 1); + test_and_write(kMax16, 0, kMax16 / 2, 1234, kMax32 / 4); +} + } // namespace art diff --git a/runtime/dex/dex_file-inl.h b/runtime/dex/dex_file-inl.h index 9b56328a71..9b14514cf4 100644 --- a/runtime/dex/dex_file-inl.h +++ b/runtime/dex/dex_file-inl.h @@ -136,7 +136,7 @@ inline const char* DexFile::GetShorty(uint32_t proto_idx) const { inline const DexFile::TryItem* DexFile::GetTryItems(const DexInstructionIterator& code_item_end, uint32_t offset) { return reinterpret_cast<const TryItem*> - (RoundUp(reinterpret_cast<uintptr_t>(&code_item_end.Inst()), 4)) + offset; + (RoundUp(reinterpret_cast<uintptr_t>(&code_item_end.Inst()), TryItem::kAlignment)) + offset; } static inline bool DexFileStringEquals(const DexFile* df1, dex::StringIndex sidx1, diff --git a/runtime/dex/dex_file.h b/runtime/dex/dex_file.h index 183d84e15d..2055f848d9 100644 --- a/runtime/dex/dex_file.h +++ b/runtime/dex/dex_file.h @@ -27,6 +27,7 @@ #include "base/macros.h" #include "base/value_object.h" #include "dex_file_types.h" +#include "dex_hidden_access_flags.h" #include "dex_instruction_iterator.h" #include "globals.h" #include "jni.h" @@ -312,6 +313,8 @@ class DexFile { // Raw try_item. struct TryItem { + static constexpr size_t kAlignment = sizeof(uint32_t); + uint32_t start_addr_; uint16_t insn_count_; uint16_t handler_off_; @@ -1252,10 +1255,16 @@ class ClassDataItemIterator { } } uint32_t GetFieldAccessFlags() const { - return GetRawMemberAccessFlags() & kAccValidFieldFlags; + return GetMemberAccessFlags() & kAccValidFieldFlags; } uint32_t GetMethodAccessFlags() const { - return GetRawMemberAccessFlags() & kAccValidMethodFlags; + return GetMemberAccessFlags() & kAccValidMethodFlags; + } + uint32_t GetMemberAccessFlags() const { + return DexHiddenAccessFlags::RemoveHiddenFlags(GetRawMemberAccessFlags()); + } + DexHiddenAccessFlags::ApiList DecodeHiddenAccessFlags() const { + return DexHiddenAccessFlags::Decode(GetRawMemberAccessFlags()); } bool MemberIsNative() const { return GetRawMemberAccessFlags() & kAccNative; diff --git a/runtime/dex/dex_file_loader.cc b/runtime/dex/dex_file_loader.cc index 10aef56125..c80ea199bc 100644 --- a/runtime/dex/dex_file_loader.cc +++ b/runtime/dex/dex_file_loader.cc @@ -94,16 +94,24 @@ bool DexFileLoader::GetMultiDexChecksums(const char* filename ATTRIBUTE_UNUSED, return false; } -std::unique_ptr<const DexFile> DexFileLoader::Open(const uint8_t* base ATTRIBUTE_UNUSED, - size_t size ATTRIBUTE_UNUSED, - const std::string& location ATTRIBUTE_UNUSED, - uint32_t location_checksum ATTRIBUTE_UNUSED, - const OatDexFile* oat_dex_file ATTRIBUTE_UNUSED, - bool verify ATTRIBUTE_UNUSED, - bool verify_checksum ATTRIBUTE_UNUSED, +std::unique_ptr<const DexFile> DexFileLoader::Open(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + bool verify_checksum, std::string* error_msg) const { - *error_msg = "UNIMPLEMENTED"; - return nullptr; + return OpenCommon(base, + size, + location, + location_checksum, + oat_dex_file, + verify, + verify_checksum, + error_msg, + /*container*/ nullptr, + /*verify_result*/ nullptr); } std::unique_ptr<const DexFile> DexFileLoader::Open(const std::string& location ATTRIBUTE_UNUSED, @@ -132,6 +140,7 @@ std::unique_ptr<const DexFile> DexFileLoader::OpenDex( const std::string& location ATTRIBUTE_UNUSED, bool verify ATTRIBUTE_UNUSED, bool verify_checksum ATTRIBUTE_UNUSED, + bool mmap_shared ATTRIBUTE_UNUSED, std::string* error_msg) const { *error_msg = "UNIMPLEMENTED"; return nullptr; @@ -153,6 +162,7 @@ std::unique_ptr<const DexFile> DexFileLoader::OpenFile( const std::string& location ATTRIBUTE_UNUSED, bool verify ATTRIBUTE_UNUSED, bool verify_checksum ATTRIBUTE_UNUSED, + bool mmap_shared ATTRIBUTE_UNUSED, std::string* error_msg) const { *error_msg = "UNIMPLEMENTED"; return nullptr; diff --git a/runtime/dex/dex_file_loader.h b/runtime/dex/dex_file_loader.h index 6f1afd636f..4e45fb03b8 100644 --- a/runtime/dex/dex_file_loader.h +++ b/runtime/dex/dex_file_loader.h @@ -97,6 +97,7 @@ class DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const; // Opens dex files from within a .jar, .zip, or .apk file @@ -182,6 +183,7 @@ class DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const; // Open all classesXXX.dex files from a zip archive. diff --git a/runtime/dex/dex_hidden_access_flags.h b/runtime/dex/dex_hidden_access_flags.h new file mode 100644 index 0000000000..16fae86b24 --- /dev/null +++ b/runtime/dex/dex_hidden_access_flags.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ +#define ART_RUNTIME_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ + +#include "base/bit_utils.h" +#include "modifiers.h" + +namespace art { + +/* This class is used for encoding and decoding access flags of DexFile members + * from the boot class path. These access flags might contain additional two bits + * of information on whether the given class member should be hidden from apps. + * + * First bit is encoded as inversion of visibility flags (public/private/protected). + * At most one can be set for any given class member. If two or three are set, + * this is interpreted as the first bit being set and actual visibility flags + * being the complement of the encoded flags. + * + * Second bit is either encoded as bit 5 for fields and non-native methods, where + * it carries no other meaning. If a method is native, bit 9 is used. + * + * Bits were selected so that they never increase the length of unsigned LEB-128 + * encoding of the access flags. + */ +class DexHiddenAccessFlags { + public: + enum ApiList { + kWhitelist = 0, + kLightGreylist, + kDarkGreylist, + kBlacklist, + }; + + static ALWAYS_INLINE ApiList Decode(uint32_t access_flags) { + DexHiddenAccessFlags flags(access_flags); + uint32_t int_value = (flags.IsFirstBitSet() ? 1 : 0) + (flags.IsSecondBitSet() ? 2 : 0); + return static_cast<ApiList>(int_value); + } + + static ALWAYS_INLINE uint32_t RemoveHiddenFlags(uint32_t access_flags) { + DexHiddenAccessFlags flags(access_flags); + flags.SetFirstBit(false); + flags.SetSecondBit(false); + return flags.GetEncoding(); + } + + static ALWAYS_INLINE uint32_t Encode(uint32_t access_flags, ApiList value) { + DexHiddenAccessFlags flags(access_flags); + uint32_t int_value = static_cast<uint32_t>(value); + flags.SetFirstBit((int_value & 1) != 0); + flags.SetSecondBit((int_value & 2) != 0); + return flags.GetEncoding(); + } + + private: + explicit DexHiddenAccessFlags(uint32_t access_flags) : access_flags_(access_flags) {} + + ALWAYS_INLINE uint32_t GetSecondFlag() { + return ((access_flags_ & kAccNative) != 0) ? kAccDexHiddenBitNative : kAccDexHiddenBit; + } + + ALWAYS_INLINE bool IsFirstBitSet() { + static_assert(IsPowerOfTwo(0u), "Following statement checks if *at most* one bit is set"); + return !IsPowerOfTwo(access_flags_ & kAccVisibilityFlags); + } + + ALWAYS_INLINE void SetFirstBit(bool value) { + if (IsFirstBitSet() != value) { + access_flags_ ^= kAccVisibilityFlags; + } + } + + ALWAYS_INLINE bool IsSecondBitSet() { + return (access_flags_ & GetSecondFlag()) != 0; + } + + ALWAYS_INLINE void SetSecondBit(bool value) { + if (value) { + access_flags_ |= GetSecondFlag(); + } else { + access_flags_ &= ~GetSecondFlag(); + } + } + + ALWAYS_INLINE uint32_t GetEncoding() const { + return access_flags_; + } + + uint32_t access_flags_; +}; + +} // namespace art + + +#endif // ART_RUNTIME_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ diff --git a/runtime/elf.h b/runtime/elf.h index 63b18c5d34..521d4a232f 100644 --- a/runtime/elf.h +++ b/runtime/elf.h @@ -64,6 +64,9 @@ constexpr char ELFMAG0 = ElfMagic[EI_MAG0]; constexpr char ELFMAG1 = ElfMagic[EI_MAG1]; constexpr char ELFMAG2 = ElfMagic[EI_MAG2]; constexpr char ELFMAG3 = ElfMagic[EI_MAG3]; +constexpr char ELFMAG[] = "\177ELF"; +constexpr int SELFMAG = 4; +constexpr int NT_PRSTATUS = 1; // END android-added for <elf.h> compat struct Elf32_Ehdr { @@ -1411,7 +1414,9 @@ struct Elf32_Sym { }; // BEGIN android-added for <elf.h> compat +static inline unsigned char ELF32_ST_BIND(unsigned char st_info) { return st_info >> 4; } static inline unsigned char ELF32_ST_TYPE(unsigned char st_info) { return st_info & 0x0f; } +static inline unsigned char ELF64_ST_BIND(unsigned char st_info) { return st_info >> 4; } static inline unsigned char ELF64_ST_TYPE(unsigned char st_info) { return st_info & 0x0f; } // END android-added for <elf.h> compat diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 49c2a15e86..9d6e5de803 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -201,13 +201,13 @@ bool FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { return true; } } + } - // We hit a signal we didn't handle. This might be something for which - // we can give more information about so call all registered handlers to - // see if it is. - if (HandleFaultByOtherHandlers(sig, info, context)) { - return true; - } + // We hit a signal we didn't handle. This might be something for which + // we can give more information about so call all registered handlers to + // see if it is. + if (HandleFaultByOtherHandlers(sig, info, context)) { + return true; } // Set a breakpoint in this function to catch unhandled signals. @@ -232,7 +232,7 @@ void FaultManager::RemoveHandler(FaultHandler* handler) { } auto it2 = std::find(other_handlers_.begin(), other_handlers_.end(), handler); if (it2 != other_handlers_.end()) { - other_handlers_.erase(it); + other_handlers_.erase(it2); return; } LOG(FATAL) << "Attempted to remove non existent handler " << handler; diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index a68227e0eb..1e0c0b16e4 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -300,7 +300,6 @@ void ConcurrentCopying::InitializePhase() { objects_moved_.StoreRelaxed(0); GcCause gc_cause = GetCurrentIteration()->GetGcCause(); if (gc_cause == kGcCauseExplicit || - gc_cause == kGcCauseForNativeAllocBlocking || gc_cause == kGcCauseCollectorTransition || GetCurrentIteration()->GetClearSoftReferences()) { force_evacuate_all_ = true; diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc index 3150781a5a..1e136bca2e 100644 --- a/runtime/gc/collector/semi_space.cc +++ b/runtime/gc/collector/semi_space.cc @@ -193,7 +193,6 @@ void SemiSpace::MarkingPhase() { if (generational_) { if (GetCurrentIteration()->GetGcCause() == kGcCauseExplicit || GetCurrentIteration()->GetGcCause() == kGcCauseForNativeAlloc || - GetCurrentIteration()->GetGcCause() == kGcCauseForNativeAllocBlocking || GetCurrentIteration()->GetClearSoftReferences()) { // If an explicit, native allocation-triggered, or last attempt // collection, collect the whole heap. diff --git a/runtime/gc/gc_cause.cc b/runtime/gc/gc_cause.cc index d88fcdcc95..508d76535e 100644 --- a/runtime/gc/gc_cause.cc +++ b/runtime/gc/gc_cause.cc @@ -33,7 +33,6 @@ const char* PrettyCause(GcCause cause) { case kGcCauseBackground: return "Background"; case kGcCauseExplicit: return "Explicit"; case kGcCauseForNativeAlloc: return "NativeAlloc"; - case kGcCauseForNativeAllocBlocking: return "NativeAllocBlocking"; case kGcCauseCollectorTransition: return "CollectorTransition"; case kGcCauseDisableMovingGc: return "DisableMovingGc"; case kGcCauseHomogeneousSpaceCompact: return "HomogeneousSpaceCompact"; diff --git a/runtime/gc/gc_cause.h b/runtime/gc/gc_cause.h index 78496f3ead..81781ceeb7 100644 --- a/runtime/gc/gc_cause.h +++ b/runtime/gc/gc_cause.h @@ -36,9 +36,6 @@ enum GcCause { // GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded. // (This may be a blocking GC depending on whether we run a non-concurrent collector). kGcCauseForNativeAlloc, - // GC triggered for a native allocation when NativeAllocationBlockingGcWatermark is exceeded. - // (This is always a blocking GC). - kGcCauseForNativeAllocBlocking, // GC triggered for a collector transition. kGcCauseCollectorTransition, // Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical). diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 9edba96ddd..6da092c365 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -128,9 +128,6 @@ static constexpr size_t kVerifyObjectAllocationStackSize = 16 * KB / sizeof(mirror::HeapReference<mirror::Object>); static constexpr size_t kDefaultAllocationStackSize = 8 * MB / sizeof(mirror::HeapReference<mirror::Object>); -// System.runFinalization can deadlock with native allocations, to deal with this, we have a -// timeout on how long we wait for finalizers to run. b/21544853 -static constexpr uint64_t kNativeAllocationFinalizeTimeout = MsToNs(250u); // For deterministic compilation, we need the heap to be at a well-known address. static constexpr uint32_t kAllocSpaceBeginForDeterministicAoT = 0x40000000; @@ -561,12 +558,6 @@ Heap::Heap(size_t initial_size, gc_complete_lock_ = new Mutex("GC complete lock"); gc_complete_cond_.reset(new ConditionVariable("GC complete condition variable", *gc_complete_lock_)); - native_blocking_gc_lock_ = new Mutex("Native blocking GC lock"); - native_blocking_gc_cond_.reset(new ConditionVariable("Native blocking GC condition variable", - *native_blocking_gc_lock_)); - native_blocking_gc_is_assigned_ = false; - native_blocking_gc_in_progress_ = false; - native_blocking_gcs_finished_ = 0; thread_flip_lock_ = new Mutex("GC thread flip lock"); thread_flip_cond_.reset(new ConditionVariable("GC thread flip condition variable", @@ -1143,7 +1134,6 @@ Heap::~Heap() { STLDeleteElements(&continuous_spaces_); STLDeleteElements(&discontinuous_spaces_); delete gc_complete_lock_; - delete native_blocking_gc_lock_; delete thread_flip_lock_; delete pending_task_lock_; delete backtrace_lock_; @@ -2556,10 +2546,6 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, // old_native_bytes_allocated_ now that GC has been triggered, resetting // new_native_bytes_allocated_ to zero in the process. old_native_bytes_allocated_.FetchAndAddRelaxed(new_native_bytes_allocated_.ExchangeRelaxed(0)); - if (gc_cause == kGcCauseForNativeAllocBlocking) { - MutexLock mu(self, *native_blocking_gc_lock_); - native_blocking_gc_in_progress_ = true; - } } DCHECK_LT(gc_type, collector::kGcTypeMax); @@ -3386,7 +3372,6 @@ collector::GcType Heap::WaitForGcToCompleteLocked(GcCause cause, Thread* self) { // it results in log spam. kGcCauseExplicit is already logged in LogGC, so avoid it here too. if (cause == kGcCauseForAlloc || cause == kGcCauseForNativeAlloc || - cause == kGcCauseForNativeAllocBlocking || cause == kGcCauseDisableMovingGc) { VLOG(gc) << "Starting a blocking GC " << cause; } @@ -3772,59 +3757,9 @@ void Heap::RunFinalization(JNIEnv* env, uint64_t timeout) { } void Heap::RegisterNativeAllocation(JNIEnv* env, size_t bytes) { - // See the REDESIGN section of go/understanding-register-native-allocation - // for an explanation of how RegisterNativeAllocation works. - size_t new_value = bytes + new_native_bytes_allocated_.FetchAndAddRelaxed(bytes); - if (new_value > NativeAllocationBlockingGcWatermark()) { - // Wait for a new GC to finish and finalizers to run, because the - // allocation rate is too high. - Thread* self = ThreadForEnv(env); - - bool run_gc = false; - { - MutexLock mu(self, *native_blocking_gc_lock_); - uint32_t initial_gcs_finished = native_blocking_gcs_finished_; - if (native_blocking_gc_in_progress_) { - // A native blocking GC is in progress from the last time the native - // allocation blocking GC watermark was exceeded. Wait for that GC to - // finish before addressing the fact that we exceeded the blocking - // watermark again. - do { - ScopedTrace trace("RegisterNativeAllocation: Wait For Prior Blocking GC Completion"); - native_blocking_gc_cond_->Wait(self); - } while (native_blocking_gcs_finished_ == initial_gcs_finished); - initial_gcs_finished++; - } - - // It's possible multiple threads have seen that we exceeded the - // blocking watermark. Ensure that only one of those threads is assigned - // to run the blocking GC. The rest of the threads should instead wait - // for the blocking GC to complete. - if (native_blocking_gcs_finished_ == initial_gcs_finished) { - if (native_blocking_gc_is_assigned_) { - do { - ScopedTrace trace("RegisterNativeAllocation: Wait For Blocking GC Completion"); - native_blocking_gc_cond_->Wait(self); - } while (native_blocking_gcs_finished_ == initial_gcs_finished); - } else { - native_blocking_gc_is_assigned_ = true; - run_gc = true; - } - } - } + size_t old_value = new_native_bytes_allocated_.FetchAndAddRelaxed(bytes); - if (run_gc) { - CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAllocBlocking, false); - RunFinalization(env, kNativeAllocationFinalizeTimeout); - CHECK(!env->ExceptionCheck()); - - MutexLock mu(self, *native_blocking_gc_lock_); - native_blocking_gc_is_assigned_ = false; - native_blocking_gc_in_progress_ = false; - native_blocking_gcs_finished_++; - native_blocking_gc_cond_->Broadcast(self); - } - } else if (new_value > NativeAllocationGcWatermark() * HeapGrowthMultiplier() && + if (old_value > NativeAllocationGcWatermark() * HeapGrowthMultiplier() && !IsGCRequestPending()) { // Trigger another GC because there have been enough native bytes // allocated since the last GC. diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 7dcf82f415..57d3d506f0 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -268,7 +268,7 @@ class Heap { REQUIRES_SHARED(Locks::mutator_lock_); void RegisterNativeAllocation(JNIEnv* env, size_t bytes) - REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*native_blocking_gc_lock_); + REQUIRES(!*gc_complete_lock_, !*pending_task_lock_); void RegisterNativeFree(JNIEnv* env, size_t bytes); // Change the allocator, updates entrypoints. @@ -1087,16 +1087,6 @@ class Heap { return max_free_; } - // How large new_native_bytes_allocated_ can grow while GC is in progress - // before we block the allocating thread to allow GC to catch up. - ALWAYS_INLINE size_t NativeAllocationBlockingGcWatermark() const { - // Historically the native allocations were bounded by growth_limit_. This - // uses that same value, dividing growth_limit_ by 2 to account for - // the fact that now the bound is relative to the number of retained - // registered native allocations rather than absolute. - return growth_limit_ / 2; - } - void TraceHeapSize(size_t heap_size); // Remove a vlog code from heap-inl.h which is transitively included in half the world. @@ -1252,23 +1242,6 @@ class Heap { // old_native_bytes_allocated_ and new_native_bytes_allocated_. Atomic<size_t> old_native_bytes_allocated_; - // Used for synchronization when multiple threads call into - // RegisterNativeAllocation and require blocking GC. - // * If a previous blocking GC is in progress, all threads will wait for - // that GC to complete, then wait for one of the threads to complete another - // blocking GC. - // * If a blocking GC is assigned but not in progress, a thread has been - // assigned to run a blocking GC but has not started yet. Threads will wait - // for the assigned blocking GC to complete. - // * If a blocking GC is not assigned nor in progress, the first thread will - // run a blocking GC and signal to other threads that blocking GC has been - // assigned. - Mutex* native_blocking_gc_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - std::unique_ptr<ConditionVariable> native_blocking_gc_cond_ GUARDED_BY(native_blocking_gc_lock_); - bool native_blocking_gc_is_assigned_ GUARDED_BY(native_blocking_gc_lock_); - bool native_blocking_gc_in_progress_ GUARDED_BY(native_blocking_gc_lock_); - uint32_t native_blocking_gcs_finished_ GUARDED_BY(native_blocking_gc_lock_); - // Number of bytes freed by thread local buffer revokes. This will // cancel out the ahead-of-time bulk counting of bytes allocated in // rosalloc thread-local buffers. It is temporarily accumulated diff --git a/runtime/leb128.h b/runtime/leb128.h index 2bfed7f539..9fb09d8fc2 100644 --- a/runtime/leb128.h +++ b/runtime/leb128.h @@ -241,7 +241,7 @@ static inline void EncodeUnsignedLeb128(Vector* dest, uint32_t value) { static inline void UpdateUnsignedLeb128(uint8_t* dest, uint32_t value) { const uint8_t* old_end = dest; uint32_t old_value = DecodeUnsignedLeb128(&old_end); - DCHECK_LE(value, old_value); + DCHECK_LE(UnsignedLeb128Size(value), UnsignedLeb128Size(old_value)); for (uint8_t* end = EncodeUnsignedLeb128(dest, value); end < old_end; end++) { // Use longer encoding than necessary to fill the allocated space. end[-1] |= 0x80; diff --git a/runtime/modifiers.h b/runtime/modifiers.h index d7d647b8fd..a72f9daad6 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -42,6 +42,12 @@ static constexpr uint32_t kAccEnum = 0x4000; // class, field, ic (1.5) static constexpr uint32_t kAccJavaFlagsMask = 0xffff; // bits set from Java sources (low 16) +// The following flags are used to insert hidden API access flags into boot +// class path dex files. They are decoded by DexFile::ClassDataItemIterator and +// removed from the access flags before used by the runtime. +static constexpr uint32_t kAccDexHiddenBit = 0x00000020; // field, method (not native) +static constexpr uint32_t kAccDexHiddenBitNative = 0x00000200; // method (native) + static constexpr uint32_t kAccConstructor = 0x00010000; // method (dex only) <(cl)init> static constexpr uint32_t kAccDeclaredSynchronized = 0x00020000; // method (dex only) static constexpr uint32_t kAccClassIsProxy = 0x00040000; // class (dex only) @@ -127,6 +133,8 @@ static constexpr uint32_t kAccValidClassFlags = kAccPublic | kAccFinal | kAccSup static constexpr uint32_t kAccValidInterfaceFlags = kAccPublic | kAccInterface | kAccAbstract | kAccSynthetic | kAccAnnotation; +static constexpr uint32_t kAccVisibilityFlags = kAccPublic | kAccPrivate | kAccProtected; + } // namespace art #endif // ART_RUNTIME_MODIFIERS_H_ diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc index 3e8040bfa5..ed0eb97da1 100644 --- a/runtime/native/dalvik_system_VMStack.cc +++ b/runtime/native/dalvik_system_VMStack.cc @@ -160,12 +160,22 @@ static jobjectArray VMStack_getThreadStackTrace(JNIEnv* env, jclass, jobject jav return Thread::InternalStackTraceToStackTraceElementArray(soa, trace); } +static jobjectArray VMStack_getAnnotatedThreadStackTrace(JNIEnv* env, jclass, jobject javaThread) { + ScopedFastNativeObjectAccess soa(env); + auto fn = [](Thread* thread, const ScopedFastNativeObjectAccess& soaa) + REQUIRES_SHARED(Locks::mutator_lock_) -> jobjectArray { + return thread->CreateAnnotatedStackTrace(soaa); + }; + return GetThreadStack(soa, javaThread, fn); +} + static JNINativeMethod gMethods[] = { FAST_NATIVE_METHOD(VMStack, fillStackTraceElements, "(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"), FAST_NATIVE_METHOD(VMStack, getCallingClassLoader, "()Ljava/lang/ClassLoader;"), FAST_NATIVE_METHOD(VMStack, getClosestUserClassLoader, "()Ljava/lang/ClassLoader;"), FAST_NATIVE_METHOD(VMStack, getStackClass2, "()Ljava/lang/Class;"), FAST_NATIVE_METHOD(VMStack, getThreadStackTrace, "(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"), + FAST_NATIVE_METHOD(VMStack, getAnnotatedThreadStackTrace, "(Ljava/lang/Thread;)[Ldalvik/system/AnnotatedStackTraceElement;"), }; void register_dalvik_system_VMStack(JNIEnv* env) { diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 3ac3d03e90..2f60162c77 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -327,6 +327,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .WithType<bool>() .WithValueMap({{"false", false}, {"true", true}}) .IntoKey(M::SlowDebug) + .Define("-Xtarget-sdk-version:_") + .WithType<int>() + .IntoKey(M::TargetSdkVersion) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 377e0a3fca..007d361976 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -250,7 +250,7 @@ Runtime::Runtime() preinitialization_transactions_(), verify_(verifier::VerifyMode::kNone), allow_dex_file_fallback_(true), - target_sdk_version_(0), + target_sdk_version_(kUnsetSdkVersion), implicit_null_checks_(false), implicit_so_checks_(false), implicit_suspend_checks_(false), @@ -1166,6 +1166,8 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { verify_ = runtime_options.GetOrDefault(Opt::Verify); allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback); + target_sdk_version_ = runtime_options.GetOrDefault(Opt::TargetSdkVersion); + no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain); force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge); diff --git a/runtime/runtime.h b/runtime/runtime.h index 3e055c3f84..6d2887cc42 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -709,6 +709,8 @@ class Runtime { return jdwp_provider_; } + static constexpr int32_t kUnsetSdkVersion = 0u; + private: static void InitPlatformSignalHandlers(); diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 1dd3de5039..3996989920 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -118,6 +118,7 @@ RUNTIME_OPTIONS_KEY (std::vector<std::string>, \ ImageCompilerOptions) // -Ximage-compiler-option ... RUNTIME_OPTIONS_KEY (verifier::VerifyMode, \ Verify, verifier::VerifyMode::kEnable) +RUNTIME_OPTIONS_KEY (int, TargetSdkVersion, Runtime::kUnsetSdkVersion) RUNTIME_OPTIONS_KEY (std::string, NativeBridge) RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) diff --git a/runtime/thread.cc b/runtime/thread.cc index 9f4e5441a5..46cb751b93 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2743,6 +2743,199 @@ jobjectArray Thread::InternalStackTraceToStackTraceElementArray( return result; } +jobjectArray Thread::CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const { + // This code allocates. Do not allow it to operate with a pending exception. + if (IsExceptionPending()) { + return nullptr; + } + + // If flip_function is not null, it means we have run a checkpoint + // before the thread wakes up to execute the flip function and the + // thread roots haven't been forwarded. So the following access to + // the roots (locks or methods in the frames) would be bad. Run it + // here. TODO: clean up. + // Note: copied from DumpJavaStack. + { + Thread* this_thread = const_cast<Thread*>(this); + Closure* flip_func = this_thread->GetFlipFunction(); + if (flip_func != nullptr) { + flip_func->Run(this_thread); + } + } + + class CollectFramesAndLocksStackVisitor : public MonitorObjectsStackVisitor { + public: + CollectFramesAndLocksStackVisitor(const ScopedObjectAccessAlreadyRunnable& soaa_in, + Thread* self, + Context* context) + : MonitorObjectsStackVisitor(self, context), + wait_jobject_(soaa_in.Env(), nullptr), + block_jobject_(soaa_in.Env(), nullptr), + soaa_(soaa_in) {} + + protected: + VisitMethodResult StartMethod(ArtMethod* m, size_t frame_nr ATTRIBUTE_UNUSED) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::StackTraceElement> obj = CreateStackTraceElement( + soaa_, m, GetDexPc(/* abort on error */ false)); + if (obj == nullptr) { + return VisitMethodResult::kEndStackWalk; + } + stack_trace_elements_.emplace_back(soaa_.Env(), soaa_.AddLocalReference<jobject>(obj.Ptr())); + return VisitMethodResult::kContinueMethod; + } + + VisitMethodResult EndMethod(ArtMethod* m ATTRIBUTE_UNUSED) OVERRIDE { + lock_objects_.push_back({}); + lock_objects_[lock_objects_.size() - 1].swap(frame_lock_objects_); + + DCHECK_EQ(lock_objects_.size(), stack_trace_elements_.size()); + + return VisitMethodResult::kContinueMethod; + } + + void VisitWaitingObject(mirror::Object* obj, ThreadState state ATTRIBUTE_UNUSED) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) { + wait_jobject_.reset(soaa_.AddLocalReference<jobject>(obj)); + } + void VisitSleepingObject(mirror::Object* obj) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) { + wait_jobject_.reset(soaa_.AddLocalReference<jobject>(obj)); + } + void VisitBlockedOnObject(mirror::Object* obj, + ThreadState state ATTRIBUTE_UNUSED, + uint32_t owner_tid ATTRIBUTE_UNUSED) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) { + block_jobject_.reset(soaa_.AddLocalReference<jobject>(obj)); + } + void VisitLockedObject(mirror::Object* obj) + OVERRIDE + REQUIRES_SHARED(Locks::mutator_lock_) { + frame_lock_objects_.emplace_back(soaa_.Env(), soaa_.AddLocalReference<jobject>(obj)); + } + + public: + std::vector<ScopedLocalRef<jobject>> stack_trace_elements_; + ScopedLocalRef<jobject> wait_jobject_; + ScopedLocalRef<jobject> block_jobject_; + std::vector<std::vector<ScopedLocalRef<jobject>>> lock_objects_; + + private: + const ScopedObjectAccessAlreadyRunnable& soaa_; + + std::vector<ScopedLocalRef<jobject>> frame_lock_objects_; + }; + + std::unique_ptr<Context> context(Context::Create()); + CollectFramesAndLocksStackVisitor dumper(soa, const_cast<Thread*>(this), context.get()); + dumper.WalkStack(); + + // There should not be a pending exception. Otherwise, return with it pending. + if (IsExceptionPending()) { + return nullptr; + } + + // Now go and create Java arrays. + + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + + StackHandleScope<6> hs(soa.Self()); + mirror::Class* aste_array_class = class_linker->FindClass( + soa.Self(), + "[Ldalvik/system/AnnotatedStackTraceElement;", + ScopedNullHandle<mirror::ClassLoader>()); + if (aste_array_class == nullptr) { + return nullptr; + } + Handle<mirror::Class> h_aste_array_class(hs.NewHandle<mirror::Class>(aste_array_class)); + + mirror::Class* o_array_class = class_linker->FindClass(soa.Self(), + "[Ljava/lang/Object;", + ScopedNullHandle<mirror::ClassLoader>()); + if (o_array_class == nullptr) { + // This should not fail in a healthy runtime. + soa.Self()->AssertPendingException(); + return nullptr; + } + Handle<mirror::Class> h_o_array_class(hs.NewHandle<mirror::Class>(o_array_class)); + + Handle<mirror::Class> h_aste_class(hs.NewHandle<mirror::Class>( + h_aste_array_class->GetComponentType())); + ArtField* stack_trace_element_field = h_aste_class->FindField( + soa.Self(), h_aste_class.Get(), "stackTraceElement", "Ljava/lang/StackTraceElement;"); + DCHECK(stack_trace_element_field != nullptr); + ArtField* held_locks_field = h_aste_class->FindField( + soa.Self(), h_aste_class.Get(), "heldLocks", "[Ljava/lang/Object;"); + DCHECK(held_locks_field != nullptr); + ArtField* blocked_on_field = h_aste_class->FindField( + soa.Self(), h_aste_class.Get(), "blockedOn", "Ljava/lang/Object;"); + DCHECK(blocked_on_field != nullptr); + + size_t length = dumper.stack_trace_elements_.size(); + ObjPtr<mirror::ObjectArray<mirror::Object>> array = + mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), aste_array_class, length); + if (array == nullptr) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } + + ScopedLocalRef<jobjectArray> result(soa.Env(), soa.Env()->AddLocalReference<jobjectArray>(array)); + + MutableHandle<mirror::Object> handle(hs.NewHandle<mirror::Object>(nullptr)); + MutableHandle<mirror::ObjectArray<mirror::Object>> handle2( + hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr)); + for (size_t i = 0; i != length; ++i) { + handle.Assign(h_aste_class->AllocObject(soa.Self())); + if (handle == nullptr) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } + + // Set stack trace element. + stack_trace_element_field->SetObject<false>( + handle.Get(), soa.Decode<mirror::Object>(dumper.stack_trace_elements_[i].get())); + + // Create locked-on array. + if (!dumper.lock_objects_[i].empty()) { + handle2.Assign(mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), + h_o_array_class.Get(), + dumper.lock_objects_[i].size())); + if (handle2 == nullptr) { + soa.Self()->AssertPendingOOMException(); + return nullptr; + } + int32_t j = 0; + for (auto& scoped_local : dumper.lock_objects_[i]) { + if (scoped_local == nullptr) { + continue; + } + handle2->Set(j, soa.Decode<mirror::Object>(scoped_local.get())); + DCHECK(!soa.Self()->IsExceptionPending()); + j++; + } + held_locks_field->SetObject<false>(handle.Get(), handle2.Get()); + } + + // Set blocked-on object. + if (i == 0) { + if (dumper.block_jobject_ != nullptr) { + blocked_on_field->SetObject<false>( + handle.Get(), soa.Decode<mirror::Object>(dumper.block_jobject_.get())); + } + } + + ScopedLocalRef<jobject> elem(soa.Env(), soa.AddLocalReference<jobject>(handle.Get())); + soa.Env()->SetObjectArrayElement(result.get(), i, elem.get()); + DCHECK(!soa.Self()->IsExceptionPending()); + } + + return result.release(); +} + void Thread::ThrowNewExceptionF(const char* exception_class_descriptor, const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/runtime/thread.h b/runtime/thread.h index 1e89887c3e..426d27d1b4 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -599,6 +599,9 @@ class Thread { jobjectArray output_array = nullptr, int* stack_depth = nullptr) REQUIRES_SHARED(Locks::mutator_lock_); + jobjectArray CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const + REQUIRES_SHARED(Locks::mutator_lock_); + bool HasDebuggerShadowFrames() const { return tlsPtr_.frame_id_to_shadow_frame != nullptr; } diff --git a/runtime/utils.h b/runtime/utils.h index 789498ce09..7402c12280 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -289,6 +289,20 @@ static inline void CheckedCall(const Func& function, const char* what, Args... a } } +// Hash bytes using a relatively fast hash. +static inline size_t HashBytes(const uint8_t* data, size_t len) { + size_t hash = 0x811c9dc5; + for (uint32_t i = 0; i < len; ++i) { + hash = (hash * 16777619) ^ data[i]; + } + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + return hash; +} + } // namespace art #endif // ART_RUNTIME_UTILS_H_ diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index 4687a393e2..4e45128420 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -84,8 +84,8 @@ class VdexFile { private: static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; - // Last update: Side table for debug info offsets in compact dex. - static constexpr uint8_t kVdexVersion[] = { '0', '1', '4', '\0' }; + // Last update: Use efficient encoding for compact dex code item fields + static constexpr uint8_t kVdexVersion[] = { '0', '1', '5', '\0' }; uint8_t magic_[4]; uint8_t version_[4]; diff --git a/test/004-NativeAllocations/src-art/Main.java b/test/004-NativeAllocations/src-art/Main.java index 8712755125..29f907de0b 100644 --- a/test/004-NativeAllocations/src-art/Main.java +++ b/test/004-NativeAllocations/src-art/Main.java @@ -14,82 +14,109 @@ * limitations under the License. */ -import java.lang.reflect.*; import java.lang.Runtime; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.PhantomReference; import dalvik.system.VMRuntime; public class Main { - static Object nativeLock = new Object(); static Object deadlockLock = new Object(); - static boolean aboutToDeadlockLock = false; - static int nativeBytes = 0; - static Object runtime; - static Method register_native_allocation; - static Method register_native_free; - static long maxMem = 0; - - static class NativeAllocation { - private int bytes; - - NativeAllocation(int bytes, boolean testingDeadlock) throws Exception { - this.bytes = bytes; - register_native_allocation.invoke(runtime, bytes); - - // Register native allocation can only provide guarantees bounding - // the maximum outstanding allocations if finalizers don't time - // out. In case finalizers have timed out, wait longer for them - // now to complete so we can test the guarantees. - if (!testingDeadlock) { - VMRuntime.runFinalization(0); - } + static VMRuntime runtime = VMRuntime.getRuntime(); + static volatile boolean aboutToDeadlock = false; - synchronized (nativeLock) { - if (!testingDeadlock) { - nativeBytes += bytes; - if (nativeBytes > 2 * maxMem) { - throw new OutOfMemoryError(); - } - } - } - } + // Save ref as a static field to ensure it doesn't get GC'd before the + // referent is enqueued. + static PhantomReference ref = null; + static class DeadlockingFinalizer { protected void finalize() throws Exception { - synchronized (nativeLock) { - nativeBytes -= bytes; - } - register_native_free.invoke(runtime, bytes); - aboutToDeadlockLock = true; - synchronized (deadlockLock) { - } + aboutToDeadlock = true; + synchronized (deadlockLock) { } + } + } + + private static void allocateDeadlockingFinalizer() { + new DeadlockingFinalizer(); + } + + public static PhantomReference allocPhantom(ReferenceQueue<Object> queue) { + return new PhantomReference(new Object(), queue); + } + + // Test that calling registerNativeAllocation triggers a GC eventually + // after a substantial number of registered native bytes. + private static void checkRegisterNativeAllocation() throws Exception { + long maxMem = Runtime.getRuntime().maxMemory(); + int size = (int)(maxMem / 32); + int allocationCount = 256; + int maxExpectedGcDurationMs = 2000; + + ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); + ref = allocPhantom(queue); + long total = 0; + for (int i = 0; !ref.isEnqueued() && i < allocationCount; ++i) { + runtime.registerNativeAllocation(size); + total += size; + + // Sleep a little bit to ensure not all of the calls to + // registerNativeAllocation complete while GC is in the process of + // running. + Thread.sleep(maxExpectedGcDurationMs / allocationCount); + } + + // Wait up to maxExpectedGcDurationMs to give GC a chance to finish + // running. If the reference isn't enqueued after that, then it is + // pretty unlikely (though technically still possible) that GC was + // triggered as intended. + if (queue.remove(maxExpectedGcDurationMs) == null) { + throw new RuntimeException("GC failed to complete"); + } + + while (total > 0) { + runtime.registerNativeFree(size); + total -= size; + } + } + + // Call registerNativeAllocation repeatedly at a high rate to trigger the + // case of blocking registerNativeAllocation. + private static void triggerBlockingRegisterNativeAllocation() throws Exception { + long maxMem = Runtime.getRuntime().maxMemory(); + int size = (int)(maxMem / 32); + int allocationCount = 256; + + long total = 0; + for (int i = 0; i < allocationCount; ++i) { + runtime.registerNativeAllocation(size); + total += size; + } + + while (total > 0) { + runtime.registerNativeFree(size); + total -= size; } } public static void main(String[] args) throws Exception { - Class<?> vm_runtime = Class.forName("dalvik.system.VMRuntime"); - Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime"); - runtime = get_runtime.invoke(null); - register_native_allocation = vm_runtime.getDeclaredMethod("registerNativeAllocation", Integer.TYPE); - register_native_free = vm_runtime.getDeclaredMethod("registerNativeFree", Integer.TYPE); - maxMem = Runtime.getRuntime().maxMemory(); - int count = 16; - int size = (int)(maxMem / 2 / count); - int allocation_count = 256; - NativeAllocation[] allocations = new NativeAllocation[count]; - for (int i = 0; i < allocation_count; ++i) { - allocations[i % count] = new NativeAllocation(size, false); + // Test that registerNativeAllocation triggers GC. + // Run this a few times in a loop to reduce the chances that the test + // is flaky and make sure registerNativeAllocation continues to work + // after the first GC is triggered. + for (int i = 0; i < 20; ++i) { + checkRegisterNativeAllocation(); } - // Test that we don't get a deadlock if we are holding nativeLock. If there is no timeout, - // then we will get a finalizer timeout exception. - aboutToDeadlockLock = false; + + // Test that we don't get a deadlock if we call + // registerNativeAllocation with a blocked finalizer. synchronized (deadlockLock) { - for (int i = 0; aboutToDeadlockLock != true; ++i) { - allocations[i % count] = new NativeAllocation(size, true); + allocateDeadlockingFinalizer(); + while (!aboutToDeadlock) { + checkRegisterNativeAllocation(); } + // Do more allocations now that the finalizer thread is deadlocked so that we force - // finalization and timeout. - for (int i = 0; i < 10; ++i) { - allocations[i % count] = new NativeAllocation(size, true); - } + // finalization and timeout. + triggerBlockingRegisterNativeAllocation(); } System.out.println("Test complete"); } diff --git a/test/168-vmstack-annotated/expected.txt b/test/168-vmstack-annotated/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/168-vmstack-annotated/expected.txt diff --git a/test/168-vmstack-annotated/info.txt b/test/168-vmstack-annotated/info.txt new file mode 100644 index 0000000000..d849bc31ed --- /dev/null +++ b/test/168-vmstack-annotated/info.txt @@ -0,0 +1 @@ +Regression test for b/68703210 diff --git a/test/168-vmstack-annotated/run b/test/168-vmstack-annotated/run new file mode 100644 index 0000000000..93654113e6 --- /dev/null +++ b/test/168-vmstack-annotated/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# 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. + +# Use a smaller heap so it's easier to potentially fill up. +exec ${RUN} $@ --runtime-option -Xmx2m diff --git a/test/168-vmstack-annotated/src/Main.java b/test/168-vmstack-annotated/src/Main.java new file mode 100644 index 0000000000..8234f945c0 --- /dev/null +++ b/test/168-vmstack-annotated/src/Main.java @@ -0,0 +1,225 @@ +/* + * 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.Thread.State; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +public class Main { + + static class Runner implements Runnable { + List<Object> locks; + List<CyclicBarrier> barriers; + + public Runner(List<Object> locks, List<CyclicBarrier> barriers) { + this.locks = locks; + this.barriers = barriers; + } + + @Override + public void run() { + step(locks, barriers); + } + + private void step(List<Object> l, List<CyclicBarrier> b) { + if (l.isEmpty()) { + // Nothing to do, sleep indefinitely. + try { + Thread.sleep(100000000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } else { + Object lockObject = l.remove(0); + CyclicBarrier barrierObject = b.remove(0); + + if (lockObject == null) { + // No lock object: only take barrier, recurse. + try { + barrierObject.await(); + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + step(l, b); + } else if (barrierObject != null) { + // Have barrier: sync, wait and recurse. + synchronized(lockObject) { + try { + barrierObject.await(); + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + step(l, b); + } + } else { + // Sync, and get next step (which is assumed to have object and barrier). + synchronized (lockObject) { + Object lockObject2 = l.remove(0); + CyclicBarrier barrierObject2 = b.remove(0); + synchronized(lockObject2) { + try { + barrierObject2.await(); + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + step(l, b); + } + } + } + } + } + } + + public static void main(String[] args) throws Exception { + try { + testCluster1(); + } catch (Exception e) { + Map<Thread,StackTraceElement[]> stacks = Thread.getAllStackTraces(); + for (Map.Entry<Thread,StackTraceElement[]> entry : stacks.entrySet()) { + System.out.println(entry.getKey()); + System.out.println(Arrays.toString(entry.getValue())); + } + throw e; + } + } + + private static void testCluster1() throws Exception { + // Test setup (at deadlock): + // + // Thread 1: + // #0 step: synchornized(o3) { synchronized(o2) } + // #1 step: synchronized(o1) + // + // Thread 2: + // #0 step: synchronized(o1) + // #1 step: synchronized(o4) { synchronized(o2) } + // + LinkedList<Object> l1 = new LinkedList<>(); + LinkedList<CyclicBarrier> b1 = new LinkedList<>(); + LinkedList<Object> l2 = new LinkedList<>(); + LinkedList<CyclicBarrier> b2 = new LinkedList<>(); + + Object o1 = new Object(); + Object o2 = new Object(); + Object o3 = new Object(); + Object o4 = new Object(); + + l1.add(o1); + l1.add(o3); + l1.add(o2); + l2.add(o4); + l2.add(o2); + l2.add(o1); + + CyclicBarrier c1 = new CyclicBarrier(3); + CyclicBarrier c2 = new CyclicBarrier(2); + b1.add(c1); + b1.add(null); + b1.add(c2); + b2.add(null); + b2.add(c1); + b2.add(c2); + + Thread t1 = new Thread(new Runner(l1, b1)); + t1.setDaemon(true); + t1.start(); + Thread t2 = new Thread(new Runner(l2, b2)); + t2.setDaemon(true); + t2.start(); + + c1.await(); + + waitNotRunnable(t1); + waitNotRunnable(t2); + Thread.sleep(250); // Unfortunately this seems necessary. :-( + + // Thread 1. + { + Object[] stack1 = getAnnotatedStack(t1); + assertBlockedOn(stack1[0], o2); // Blocked on o2. + assertLocks(stack1[0], o3); // Locked o3. + assertStackTraceElementStep(stack1[0]); + + assertBlockedOn(stack1[1], null); // Frame can't be blocked. + assertLocks(stack1[1], o1); // Locked o1. + assertStackTraceElementStep(stack1[1]); + } + + // Thread 2. + { + Object[] stack2 = getAnnotatedStack(t2); + assertBlockedOn(stack2[0], o1); // Blocked on o1. + assertLocks(stack2[0]); // Nothing locked. + assertStackTraceElementStep(stack2[0]); + + assertBlockedOn(stack2[1], null); // Frame can't be blocked. + assertLocks(stack2[1], o4, o2); // Locked o4, o2. + assertStackTraceElementStep(stack2[1]); + } + } + + private static void waitNotRunnable(Thread t) throws InterruptedException { + while (t.getState() == State.RUNNABLE) { + Thread.sleep(100); + } + } + + private static Object[] getAnnotatedStack(Thread t) throws Exception { + Class<?> vmStack = Class.forName("dalvik.system.VMStack"); + Method m = vmStack.getDeclaredMethod("getAnnotatedThreadStackTrace", Thread.class); + return (Object[]) m.invoke(null, t); + } + + private static void assertEquals(Object o1, Object o2) { + if (o1 != o2) { + throw new RuntimeException("Expected " + o1 + " == " + o2); + } + } + private static void assertLocks(Object fromTrace, Object... locks) throws Exception { + Object fieldValue = fromTrace.getClass().getDeclaredMethod("getHeldLocks"). + invoke(fromTrace); + assertEquals((Object[]) fieldValue, + (locks == null) ? null : (locks.length == 0 ? null : locks)); + } + private static void assertBlockedOn(Object fromTrace, Object block) throws Exception { + Object fieldValue = fromTrace.getClass().getDeclaredMethod("getBlockedOn"). + invoke(fromTrace); + assertEquals(fieldValue, block); + } + private static void assertEquals(Object[] o1, Object[] o2) { + if (!Arrays.equals(o1, o2)) { + throw new RuntimeException( + "Expected " + Arrays.toString(o1) + " == " + Arrays.toString(o2)); + } + } + private static void assertStackTraceElementStep(Object o) throws Exception { + Object fieldValue = o.getClass().getDeclaredMethod("getStackTraceElement").invoke(o); + if (fieldValue instanceof StackTraceElement) { + StackTraceElement elem = (StackTraceElement) fieldValue; + if (!elem.getMethodName().equals("step")) { + throw new RuntimeException("Expected step method"); + } + return; + } + throw new RuntimeException("Expected StackTraceElement " + fieldValue + " / " + o); + } +} + diff --git a/test/305-other-fault-handler/expected.txt b/test/305-other-fault-handler/expected.txt new file mode 100644 index 0000000000..6221e8e853 --- /dev/null +++ b/test/305-other-fault-handler/expected.txt @@ -0,0 +1,2 @@ +JNI_OnLoad called +Passed! diff --git a/test/305-other-fault-handler/fault_handler.cc b/test/305-other-fault-handler/fault_handler.cc new file mode 100644 index 0000000000..f04832613b --- /dev/null +++ b/test/305-other-fault-handler/fault_handler.cc @@ -0,0 +1,102 @@ +/* + * 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. + */ + + +#include <atomic> +#include <memory> + +#include <jni.h> +#include <signal.h> +#include <stdint.h> +#include <sys/mman.h> + +#include "fault_handler.h" +#include "globals.h" +#include "mem_map.h" + +namespace art { + +class TestFaultHandler FINAL : public FaultHandler { + public: + explicit TestFaultHandler(FaultManager* manager) + : FaultHandler(manager), + map_error_(""), + target_map_(MemMap::MapAnonymous("test-305-mmap", + /* addr */ nullptr, + /* byte_count */ kPageSize, + /* prot */ PROT_NONE, + /* low_4gb */ false, + /* reuse */ false, + /* error_msg */ &map_error_, + /* use_ashmem */ false)), + was_hit_(false) { + CHECK(target_map_ != nullptr) << "Unable to create segfault target address " << map_error_; + manager_->AddHandler(this, /*in_generated_code*/false); + } + + virtual ~TestFaultHandler() { + manager_->RemoveHandler(this); + } + + bool Action(int sig, siginfo_t* siginfo, void* context ATTRIBUTE_UNUSED) OVERRIDE { + CHECK_EQ(sig, SIGSEGV); + CHECK_EQ(reinterpret_cast<uint32_t*>(siginfo->si_addr), + GetTargetPointer()) << "Segfault on unexpected address!"; + CHECK(!was_hit_) << "Recursive signal!"; + was_hit_ = true; + + LOG(INFO) << "SEGV Caught. mprotecting map."; + CHECK(target_map_->Protect(PROT_READ | PROT_WRITE)) << "Failed to mprotect R/W"; + LOG(INFO) << "Setting value to be read."; + *GetTargetPointer() = kDataValue; + LOG(INFO) << "Changing prot to be read-only."; + CHECK(target_map_->Protect(PROT_READ)) << "Failed to mprotect R-only"; + return true; + } + + void CauseSegfault() { + CHECK_EQ(target_map_->GetProtect(), PROT_NONE); + + // This will segfault. The handler should deal with it though and we will get a value out of it. + uint32_t data = *GetTargetPointer(); + + // Prevent re-ordering around the *GetTargetPointer by the compiler + std::atomic_signal_fence(std::memory_order_seq_cst); + + CHECK(was_hit_); + CHECK_EQ(data, kDataValue) << "Unexpected read value from mmap"; + CHECK_EQ(target_map_->GetProtect(), PROT_READ); + LOG(INFO) << "Success!"; + } + + private: + uint32_t* GetTargetPointer() { + return reinterpret_cast<uint32_t*>(target_map_->Begin() + 8); + } + + static constexpr uint32_t kDataValue = 0xDEADBEEF; + + std::string map_error_; + std::unique_ptr<MemMap> target_map_; + bool was_hit_; +}; + +extern "C" JNIEXPORT void JNICALL Java_Main_runFaultHandlerTest(JNIEnv*, jclass) { + std::unique_ptr<TestFaultHandler> handler(new TestFaultHandler(&fault_manager)); + handler->CauseSegfault(); +} + +} // namespace art diff --git a/test/305-other-fault-handler/info.txt b/test/305-other-fault-handler/info.txt new file mode 100644 index 0000000000..656c8bd406 --- /dev/null +++ b/test/305-other-fault-handler/info.txt @@ -0,0 +1,3 @@ +Test that we correctly handle basic non-generated-code fault handlers + +Tests that we can use and remove these handlers and they can change mappings. diff --git a/test/305-other-fault-handler/src/Main.java b/test/305-other-fault-handler/src/Main.java new file mode 100644 index 0000000000..13a6fef730 --- /dev/null +++ b/test/305-other-fault-handler/src/Main.java @@ -0,0 +1,25 @@ +/* + * 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 Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + runFaultHandlerTest(); + System.out.println("Passed!"); + } + + public static native void runFaultHandlerTest(); +} diff --git a/test/672-checker-throw-method/expected.txt b/test/672-checker-throw-method/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/672-checker-throw-method/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/672-checker-throw-method/info.txt b/test/672-checker-throw-method/info.txt new file mode 100644 index 0000000000..250810be15 --- /dev/null +++ b/test/672-checker-throw-method/info.txt @@ -0,0 +1 @@ +Test detecting throwing methods for code sinking. diff --git a/test/672-checker-throw-method/src/Main.java b/test/672-checker-throw-method/src/Main.java new file mode 100644 index 0000000000..ceb5eb784c --- /dev/null +++ b/test/672-checker-throw-method/src/Main.java @@ -0,0 +1,244 @@ +/* + * 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. + */ + +/** + * Tests for detecting throwing methods for code sinking. + */ +public class Main { + + // + // Some "runtime library" methods. + // + + static private void doThrow(String par) { + throw new Error("you are null: " + par); + } + + static private void checkNotNullDirect(Object obj, String par) { + if (obj == null) + throw new Error("you are null: " + par); + } + + static private void checkNotNullSplit(Object obj, String par) { + if (obj == null) + doThrow(par); + } + + // + // Various ways of enforcing non-null parameter. + // In all cases, par should be subject to code sinking. + // + + /// CHECK-START: void Main.doit1(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + // + /// CHECK-START: void Main.doit1(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + static public void doit1(int[] a) { + String par = "a"; + if (a == null) + throw new Error("you are null: " + par); + for (int i = 0; i < a.length; i++) { + a[i] = 1; + } + } + + /// CHECK-START: void Main.doit2(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + // + /// CHECK-START: void Main.doit2(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + static public void doit2(int[] a) { + String par = "a"; + if (a == null) + doThrow(par); + for (int i = 0; i < a.length; i++) { + a[i] = 2; + } + } + + /// CHECK-START: void Main.doit3(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + // + /// CHECK-START: void Main.doit3(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + static public void doit3(int[] a) { + String par = "a"; + checkNotNullDirect(a, par); + for (int i = 0; i < a.length; i++) { + a[i] = 3; + } + } + + /// CHECK-START: void Main.doit4(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + // + /// CHECK-START: void Main.doit4(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeStaticOrDirect [<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + static public void doit4(int[] a) { + String par = "a"; + checkNotNullSplit(a, par); // resembles Kotlin runtime lib + // (test is lined, doThrow is not) + for (int i = 0; i < a.length; i++) { + a[i] = 4; + } + } + + // Ensures Phi values are merged properly. + static public int doit5(int[] a) { + int t = 100; + String par = "a"; + if (a == null) { + doThrow(par); + } else { + t = 1000; + } + for (int i = 0; i < a.length; i++) { + a[i] = 5; + } + // Phi on t, even though doThrow never reaches. + return t; + } + + // + // Test driver. + // + + static public void main(String[] args) { + int[] a = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + } + + try { + doit1(null); + System.out.println("should not reach this!"); + } catch (Error e) { + doit1(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(1, a[i]); + } + + try { + doit2(null); + System.out.println("should not reach this!"); + } catch (Error e) { + doit2(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(2, a[i]); + } + + try { + doit3(null); + System.out.println("should not reach this!"); + } catch (Error e) { + doit3(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(3, a[i]); + } + + try { + doit4(null); + System.out.println("should not reach this!"); + } catch (Error e) { + doit4(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(4, a[i]); + } + + try { + doit5(null); + System.out.println("should not reach this!"); + } catch (Error e) { + expectEquals(1000, doit5(a)); + } + for (int i = 0; i < 100; i++) { + expectEquals(5, a[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/673-checker-throw-vmethod/expected.txt b/test/673-checker-throw-vmethod/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/673-checker-throw-vmethod/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/673-checker-throw-vmethod/info.txt b/test/673-checker-throw-vmethod/info.txt new file mode 100644 index 0000000000..250810be15 --- /dev/null +++ b/test/673-checker-throw-vmethod/info.txt @@ -0,0 +1 @@ +Test detecting throwing methods for code sinking. diff --git a/test/673-checker-throw-vmethod/src/Main.java b/test/673-checker-throw-vmethod/src/Main.java new file mode 100644 index 0000000000..d0e1591bdb --- /dev/null +++ b/test/673-checker-throw-vmethod/src/Main.java @@ -0,0 +1,219 @@ +/* + * 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. + */ + +/** + * Tests for detecting throwing methods for code sinking. + */ +public class Main { + + // + // Some "runtime library" methods. + // + + public final void doThrow(String par) { + throw new Error("you are null: " + par); + } + + public final void checkNotNullDirect(Object obj, String par) { + if (obj == null) + throw new Error("you are null: " + par); + } + + public final void checkNotNullSplit(Object obj, String par) { + if (obj == null) + doThrow(par); + } + + // + // Various ways of enforcing non-null parameter. + // In all cases, par should be subject to code sinking. + // + + /// CHECK-START: void Main.doit1(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + // + /// CHECK-START: void Main.doit1(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + public void doit1(int[] a) { + String par = "a"; + if (a == null) + throw new Error("you are null: " + par); + for (int i = 0; i < a.length; i++) { + a[i] = 1; + } + } + + /// CHECK-START: void Main.doit2(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + // + /// CHECK-START: void Main.doit2(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + public void doit2(int[] a) { + String par = "a"; + if (a == null) + doThrow(par); + for (int i = 0; i < a.length; i++) { + a[i] = 2; + } + } + + /// CHECK-START: void Main.doit3(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + // + /// CHECK-START: void Main.doit3(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] + /// CHECK: Throw + /// CHECK: end_block + public void doit3(int[] a) { + String par = "a"; + checkNotNullDirect(a, par); + for (int i = 0; i < a.length; i++) { + a[i] = 3; + } + } + + /// CHECK-START: void Main.doit4(int[]) code_sinking (before) + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + // + /// CHECK-START: void Main.doit4(int[]) code_sinking (after) + /// CHECK: begin_block + /// CHECK: <<Tst:z\d+>> NotEqual + /// CHECK: If [<<Tst>>] + /// CHECK: end_block + /// CHECK: begin_block + /// CHECK: <<Str:l\d+>> LoadString + /// CHECK: InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow + /// CHECK: end_block + public void doit4(int[] a) { + String par = "a"; + checkNotNullSplit(a, par); + for (int i = 0; i < a.length; i++) { + a[i] = 4; + } + } + + // + // Test driver. + // + + static public void main(String[] args) { + int[] a = new int[100]; + for (int i = 0; i < 100; i++) { + a[i] = 0; + } + + Main m = new Main(); + + try { + m.doit1(null); + System.out.println("should not reach this!"); + } catch (Error e) { + m.doit1(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(1, a[i]); + } + + try { + m.doit2(null); + System.out.println("should not reach this!"); + } catch (Error e) { + m.doit2(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(2, a[i]); + } + + try { + m.doit3(null); + System.out.println("should not reach this!"); + } catch (Error e) { + m.doit3(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(3, a[i]); + } + + try { + m.doit4(null); + System.out.println("should not reach this!"); + } catch (Error e) { + m.doit4(a); + } + for (int i = 0; i < 100; i++) { + expectEquals(4, a[i]); + } + + System.out.println("passed"); + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/Android.bp b/test/Android.bp index f5ca2f0338..49a34a1246 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -369,6 +369,7 @@ cc_defaults { "154-gc-loop/heap_interface.cc", "167-visit-locks/visit_locks.cc", "203-multi-checkpoint/multi_checkpoint.cc", + "305-other-fault-handler/fault_handler.cc", "454-get-vreg/get_vreg_jni.cc", "457-regs/regs_jni.cc", "461-get-reference-vreg/get_reference_vreg_jni.cc", diff --git a/test/HiddenApi/Main.java b/test/HiddenApi/Main.java new file mode 100644 index 0000000000..187dd6e599 --- /dev/null +++ b/test/HiddenApi/Main.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +class Main { + public int ifield; + private static Object sfield; + + void imethod(long x) {} + public static void smethod(Object x) {} + + public native void inmethod(char x); + protected native static void snmethod(Integer x); +} diff --git a/test/knownfailures.json b/test/knownfailures.json index 9db8e9df0a..b39d6072fa 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -412,7 +412,8 @@ { "tests": [ "961-default-iface-resolution-gen", - "964-default-iface-init-gen" + "964-default-iface-init-gen", + "968-default-partial-compile-gen" ], "description": ["Tests that just take too long with jvmti-stress"], "variant": "jvmti-stress | redefine-stress | trace-stress | step-stress" @@ -647,6 +648,13 @@ "bug": "b/64683522" }, { + "tests": ["628-vdex", + "629-vdex-speed", + "634-vdex-duplicate"], + "variant": "cdex-fast", + "description": ["Tests that depend on input-vdex are not supported with compact dex"] + }, + { "tests": "661-oat-writer-layout", "variant": "interp-ac | interpreter | jit | no-dex2oat | no-prebuild | no-image | trace | redefine-stress | jvmti-stress", "description": ["Test is designed to only check --compiler-filter=speed"] diff --git a/tools/hiddenapi/Android.bp b/tools/hiddenapi/Android.bp new file mode 100644 index 0000000000..a78bc43aa4 --- /dev/null +++ b/tools/hiddenapi/Android.bp @@ -0,0 +1,64 @@ +// +// 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. +// + +cc_defaults { + name: "hiddenapi-defaults", + host_supported: true, + device_supported: false, + defaults: ["art_defaults"], + srcs: [ + "hiddenapi.cc", + ], + + target: { + android: { + compile_multilib: "prefer32", + }, + }, + + shared_libs: [ + "libbase", + ], +} + +art_cc_binary { + name: "hiddenapi", + defaults: ["hiddenapi-defaults"], + shared_libs: [ + "libart", + ], +} + +art_cc_binary { + name: "hiddenapid", + defaults: [ + "art_debug_defaults", + "hiddenapi-defaults", + ], + shared_libs: [ + "libartd", + ], +} + +art_cc_test { + name: "art_hiddenapi_tests", + host_supported: true, + device_supported: false, + defaults: [ + "art_gtest_defaults", + ], + srcs: ["hiddenapi_test.cc"], +} diff --git a/tools/hiddenapi/README.md b/tools/hiddenapi/README.md new file mode 100644 index 0000000000..cad12126dd --- /dev/null +++ b/tools/hiddenapi/README.md @@ -0,0 +1,54 @@ +HiddenApi +========= + +This tool iterates over all class members inside given DEX files and modifies +their access flags if their signatures appear on one of two lists - greylist and +blacklist - provided as text file inputs. These access flags denote to the +runtime that the marked methods/fields should be treated as internal APIs with +access restricted only to platform code. Methods/fields not mentioned on the two +lists are assumed to be on a whitelist and left accessible by all code. + +API signatures +============== + +The methods/fields to be marked are specified in two text files (greylist, +blacklist) provided an input. Only one signature per line is allowed. + +Types are expected in their DEX format - class descriptors are to be provided in +"slash" form, e.g. "Ljava/lang/Object;", primitive types in their shorty form, +e.g. "I" for "int", and a "[" prefix denotes an array type. Lists of types do +not use any separators, e.g. "ILxyz;F" for "int, xyz, float". + +Methods are encoded as: + `class_descriptor->method_name(parameter_types)return_type` + +Fields are encoded as: + `class_descriptor->field_name:field_type` + +Bit encoding +============ + +Two bits of information are encoded in the DEX access flags. These are encoded +as unsigned LEB128 values in DEX and so as to not increase the size of the DEX, +different modifiers were chosen for different kinds of methods/fields. + +First bit is encoded as the inversion of visibility access flags (bits 2:0). +At most one of these flags can be set at any given time. Inverting these bits +therefore produces a value where at least two bits are set and there is never +any loss of information. + +Second bit is encoded differently for each given type of class member as there +is no single unused bit such that setting it would not increase the size of the +LEB128 encoding. The following bits are used: + + * bit 5 for fields as it carries no other meaning + * bit 5 for non-native methods, as `synchronized` can only be set on native + methods (the Java `synchronized` modifier is bit 17) + * bit 9 for native methods, as it carries no meaning and bit 8 (`native`) will + make the LEB128 encoding at least two bytes long + +Two following bit encoding is used to denote the membership of a method/field: + + * whitelist: `false`, `false` + * greylist: `true`, `false` + * blacklist: `true`, `true` diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..fe72bb0231 --- /dev/null +++ b/tools/hiddenapi/hiddenapi.cc @@ -0,0 +1,408 @@ +/* + * 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 <fstream> +#include <iostream> +#include <unordered_set> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" + +#include "base/unix_file/fd_file.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "dex/dex_hidden_access_flags.h" +#include "mem_map.h" +#include "os.h" + +namespace art { + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { + std::vector<std::string> command; + for (int i = 0; i < original_argc; ++i) { + command.push_back(original_argv[i]); + } + return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + android::base::StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +NO_RETURN static void Usage(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError("Usage: hiddenapi [options]..."); + UsageError(""); + UsageError(" --dex=<filename>: specify dex file whose members' access flags are to be set."); + UsageError(" At least one --dex parameter must be specified."); + UsageError(""); + UsageError(" --light-greylist=<filename>:"); + UsageError(" --dark-greylist=<filename>:"); + UsageError(" --blacklist=<filename>: text files with signatures of methods/fields to be marked"); + UsageError(" greylisted/blacklisted respectively. At least one list must be provided."); + UsageError(""); + UsageError(" --print-hidden-api: dump a list of marked methods/fields to the standard output."); + UsageError(" There is no indication which API category they belong to."); + UsageError(""); + + exit(EXIT_FAILURE); +} + +class DexClass { + public: + DexClass(const DexFile& dex_file, uint32_t idx) + : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {} + + const DexFile& GetDexFile() const { return dex_file_; } + + const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; } + + const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); } + + const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); } + + private: + const DexFile& dex_file_; + const DexFile::ClassDef& class_def_; +}; + +class DexMember { + public: + DexMember(const DexClass& klass, const ClassDataItemIterator& it) + : klass_(klass), it_(it) { + DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_, + klass_.GetClassIndex()); + } + + // Sets hidden bits in access flags and writes them back into the DEX in memory. + // Note that this will not update the cached data of ClassDataItemIterator + // until it iterates over this item again and therefore will fail a CHECK if + // it is called multiple times on the same DexMember. + void SetHidden(DexHiddenAccessFlags::ApiList value) { + const uint32_t old_flags = it_.GetRawMemberAccessFlags(); + const uint32_t new_flags = DexHiddenAccessFlags::Encode(old_flags, value); + CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags)); + + // Locate the LEB128-encoded access flags in class data. + // `ptr` initially points to the next ClassData item. We iterate backwards + // until we hit the terminating byte of the previous Leb128 value. + const uint8_t* ptr = it_.DataPointer(); + if (it_.IsAtMethod()) { + ptr = ReverseSearchUnsignedLeb128(ptr, it_.GetMethodCodeItemOffset()); + } + ptr = ReverseSearchUnsignedLeb128(ptr, old_flags); + + // Overwrite the access flags. + UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags); + } + + // Returns true if this member's API entry is in `list`. + bool IsOnApiList(const std::unordered_set<std::string>& list) const { + return list.find(GetApiEntry()) != list.end(); + } + + // Constructs a string with a unique signature of this class member. + std::string GetApiEntry() const { + std::stringstream ss; + ss << klass_.GetDescriptor() << "->"; + if (it_.IsAtMethod()) { + const DexFile::MethodId& mid = GetMethodId(); + ss << klass_.GetDexFile().GetMethodName(mid) + << klass_.GetDexFile().GetMethodSignature(mid).ToString(); + } else { + const DexFile::FieldId& fid = GetFieldId(); + ss << klass_.GetDexFile().GetFieldName(fid) << ":" + << klass_.GetDexFile().GetFieldTypeDescriptor(fid); + } + return ss.str(); + } + + private: + inline const DexFile::MethodId& GetMethodId() const { + DCHECK(it_.IsAtMethod()); + return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex()); + } + + inline const DexFile::FieldId& GetFieldId() const { + DCHECK(!it_.IsAtMethod()); + return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex()); + } + + static inline bool IsLeb128Terminator(const uint8_t* ptr) { + return *ptr <= 0x7f; + } + + // Returns the first byte of a Leb128 value assuming that: + // (1) `end_ptr` points to the first byte after the Leb128 value, and + // (2) there is another Leb128 value before this one. + // The function will fail after reading 5 bytes (the longest supported Leb128 + // encoding) to protect against situations when (2) is not satisfied. + // When a Leb128 value is discovered, it is decoded and CHECKed against `value`. + static const uint8_t* ReverseSearchUnsignedLeb128(const uint8_t* end_ptr, uint32_t expected) { + const uint8_t* ptr = end_ptr; + + // Move one byte back, check that this is the terminating byte. + ptr--; + CHECK(IsLeb128Terminator(ptr)); + + // Keep moving back while the previous byte is not a terminating byte. + // Fail after reading five bytes in case there isn't another Leb128 value + // before this one. + while (!IsLeb128Terminator(ptr - 1)) { + ptr--; + CHECK_LE((size_t) (end_ptr - ptr), 5u); + } + + // Check that the decoded value matches the `expected` value. + const uint8_t* tmp_ptr = ptr; + CHECK_EQ(DecodeUnsignedLeb128(&tmp_ptr), expected); + + return ptr; + } + + const DexClass& klass_; + const ClassDataItemIterator& it_; +}; + +class HiddenApi FINAL { + public: + HiddenApi() : print_hidden_api_(false) {} + + void ParseArgs(int argc, char** argv) { + original_argc = argc; + original_argv = argv; + + android::base::InitLogging(argv); + + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + const bool log_options = false; + if (log_options) { + LOG(INFO) << "hiddenapi: option[" << i << "]=" << argv[i]; + } + if (option == "--print-hidden-api") { + print_hidden_api_ = true; + } else if (option.starts_with("--dex=")) { + dex_paths_.push_back(option.substr(strlen("--dex=")).ToString()); + } else if (option.starts_with("--light-greylist=")) { + light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString(); + } else if (option.starts_with("--dark-greylist=")) { + dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString(); + } else if (option.starts_with("--blacklist=")) { + blacklist_path_ = option.substr(strlen("--blacklist=")).ToString(); + } else { + Usage("Unknown argument '%s'", option.data()); + } + } + } + + bool ProcessDexFiles() { + if (dex_paths_.empty()) { + Usage("No DEX files specified"); + } + + if (light_greylist_path_.empty() && dark_greylist_path_.empty() && blacklist_path_.empty()) { + Usage("No API file specified"); + } + + if (!light_greylist_path_.empty() && !OpenApiFile(light_greylist_path_, &light_greylist_)) { + return false; + } + + if (!dark_greylist_path_.empty() && !OpenApiFile(dark_greylist_path_, &dark_greylist_)) { + return false; + } + + if (!blacklist_path_.empty() && !OpenApiFile(blacklist_path_, &blacklist_)) { + return false; + } + + MemMap::Init(); + if (!OpenDexFiles()) { + return false; + } + + DCHECK(!dex_files_.empty()); + for (auto& dex_file : dex_files_) { + CategorizeAllClasses(*dex_file.get()); + } + + UpdateDexChecksums(); + return true; + } + + private: + bool OpenApiFile(const std::string& path, std::unordered_set<std::string>* list) { + DCHECK(list->empty()); + DCHECK(!path.empty()); + + std::ifstream api_file(path, std::ifstream::in); + if (api_file.fail()) { + LOG(ERROR) << "Unable to open file '" << path << "' " << strerror(errno); + return false; + } + + for (std::string line; std::getline(api_file, line);) { + list->insert(line); + } + + api_file.close(); + return true; + } + + bool OpenDexFiles() { + ArtDexFileLoader dex_loader; + DCHECK(dex_files_.empty()); + + for (const std::string& filename : dex_paths_) { + std::string error_msg; + + File fd(filename.c_str(), O_RDWR, /* check_usage */ false); + if (fd.Fd() == -1) { + LOG(ERROR) << "Unable to open file '" << filename << "': " << strerror(errno); + return false; + } + + // Memory-map the dex file with MAP_SHARED flag so that changes in memory + // propagate to the underlying file. We run dex file verification as if + // the dex file was not in boot claass path to check basic assumptions, + // such as that at most one of public/private/protected flag is set. + // We do those checks here and skip them when loading the processed file + // into boot class path. + std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(), + /* location */ filename, + /* verify */ true, + /* verify_checksum */ true, + /* mmap_shared */ true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(ERROR) << "Open failed for '" << filename << "' " << error_msg; + return false; + } + + if (!dex_file->IsStandardDexFile()) { + LOG(ERROR) << "Expected a standard dex file '" << filename << "'"; + return false; + } + + // Change the protection of the memory mapping to read-write. + if (!dex_file->EnableWrite()) { + LOG(ERROR) << "Failed to enable write permission for '" << filename << "'"; + return false; + } + + dex_files_.push_back(std::move(dex_file)); + } + return true; + } + + void CategorizeAllClasses(const DexFile& dex_file) { + for (uint32_t class_idx = 0; class_idx < dex_file.NumClassDefs(); ++class_idx) { + DexClass klass(dex_file, class_idx); + const uint8_t* klass_data = klass.GetData(); + if (klass_data == nullptr) { + continue; + } + + for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) { + DexMember member(klass, it); + + // Catagorize member and overwrite its access flags. + // Note that if a member appears on multiple API lists, it will be categorized + // as the strictest. + bool is_hidden = true; + if (member.IsOnApiList(blacklist_)) { + member.SetHidden(DexHiddenAccessFlags::kBlacklist); + } else if (member.IsOnApiList(dark_greylist_)) { + member.SetHidden(DexHiddenAccessFlags::kDarkGreylist); + } else if (member.IsOnApiList(light_greylist_)) { + member.SetHidden(DexHiddenAccessFlags::kLightGreylist); + } else { + member.SetHidden(DexHiddenAccessFlags::kWhitelist); + is_hidden = false; + } + + if (print_hidden_api_ && is_hidden) { + std::cout << member.GetApiEntry() << std::endl; + } + } + } + } + + void UpdateDexChecksums() { + for (auto& dex_file : dex_files_) { + // Obtain a writeable pointer to the dex header. + DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader()); + // Recalculate checksum and overwrite the value in the header. + header->checksum_ = dex_file->CalculateChecksum(); + } + } + + // Print signatures of APIs which have been grey-/blacklisted. + bool print_hidden_api_; + + // Paths to DEX files which should be processed. + std::vector<std::string> dex_paths_; + + // Paths to text files which contain the lists of API members. + std::string light_greylist_path_; + std::string dark_greylist_path_; + std::string blacklist_path_; + + // Opened DEX files. Note that these are opened as `const` but eventually will be written into. + std::vector<std::unique_ptr<const DexFile>> dex_files_; + + // Signatures of DEX members loaded from `light_greylist_path_`, `dark_greylist_path_`, + // `blacklist_path_`. + std::unordered_set<std::string> light_greylist_; + std::unordered_set<std::string> dark_greylist_; + std::unordered_set<std::string> blacklist_; +}; + +} // namespace art + +int main(int argc, char** argv) { + art::HiddenApi hiddenapi; + + // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError. + hiddenapi.ParseArgs(argc, argv); + return hiddenapi.ProcessDexFiles() ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc new file mode 100644 index 0000000000..63484053ac --- /dev/null +++ b/tools/hiddenapi/hiddenapi_test.cc @@ -0,0 +1,601 @@ +/* + * 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 <fstream> + +#include "base/unix_file/fd_file.h" +#include "common_runtime_test.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "exec_utils.h" +#include "zip_archive.h" + +namespace art { + +class HiddenApiTest : public CommonRuntimeTest { + protected: + std::string GetHiddenApiCmd() { + std::string file_path = GetTestAndroidRoot(); + file_path += "/bin/hiddenapi"; + if (kIsDebugBuild) { + file_path += "d"; + } + if (!OS::FileExists(file_path.c_str())) { + LOG(FATAL) << "Could not find binary " << file_path; + UNREACHABLE(); + } + return file_path; + } + + std::unique_ptr<const DexFile> RunHiddenApi(const ScratchFile& light_greylist, + const ScratchFile& dark_greylist, + const ScratchFile& blacklist, + const std::vector<std::string>& extra_args, + ScratchFile* out_dex) { + std::string error; + std::unique_ptr<ZipArchive> jar( + ZipArchive::Open(GetTestDexFileName("HiddenApi").c_str(), &error)); + if (jar == nullptr) { + LOG(FATAL) << "Could not open test file " << GetTestDexFileName("HiddenApi") << ": " << error; + UNREACHABLE(); + } + std::unique_ptr<ZipEntry> jar_classes_dex(jar->Find("classes.dex", &error)); + if (jar_classes_dex == nullptr) { + LOG(FATAL) << "Could not find classes.dex in test file " << GetTestDexFileName("HiddenApi") + << ": " << error; + UNREACHABLE(); + } else if (!jar_classes_dex->ExtractToFile(*out_dex->GetFile(), &error)) { + LOG(FATAL) << "Could not extract classes.dex from test file " + << GetTestDexFileName("HiddenApi") << ": " << error; + UNREACHABLE(); + } + + std::vector<std::string> argv_str; + argv_str.push_back(GetHiddenApiCmd()); + argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end()); + argv_str.push_back("--dex=" + out_dex->GetFilename()); + argv_str.push_back("--light-greylist=" + light_greylist.GetFilename()); + argv_str.push_back("--dark-greylist=" + dark_greylist.GetFilename()); + argv_str.push_back("--blacklist=" + blacklist.GetFilename()); + int return_code = ExecAndReturnCode(argv_str, &error); + if (return_code != 0) { + LOG(FATAL) << "HiddenApi binary exited with unexpected return code " << return_code; + } + return OpenDex(*out_dex); + } + + std::unique_ptr<const DexFile> OpenDex(const ScratchFile& file) { + ArtDexFileLoader dex_loader; + std::string error_msg; + + File fd(file.GetFilename(), O_RDONLY, /* check_usage */ false); + if (fd.Fd() == -1) { + LOG(FATAL) << "Unable to open file '" << file.GetFilename() << "': " << strerror(errno); + UNREACHABLE(); + } + + std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex( + fd.Release(), /* location */ file.GetFilename(), /* verify */ false, + /* verify_checksum */ true, /* mmap_shared */ false, &error_msg)); + if (dex_file.get() == nullptr) { + LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << error_msg; + UNREACHABLE(); + } else if (!dex_file->IsStandardDexFile()) { + LOG(FATAL) << "Expected a standard dex file '" << file.GetFilename() << "'"; + UNREACHABLE(); + } + + return dex_file; + } + + std::ofstream OpenStream(const ScratchFile& file) { + std::ofstream ofs(file.GetFilename(), std::ofstream::out); + if (ofs.fail()) { + LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << strerror(errno); + UNREACHABLE(); + } + return ofs; + } + + const DexFile::ClassDef& FindClass(const char* desc, const DexFile& dex_file) { + for (uint32_t i = 0; i < dex_file.NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(i); + if (strcmp(desc, dex_file.GetClassDescriptor(class_def)) == 0) { + return class_def; + } + } + LOG(FATAL) << "Could not find class " << desc; + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetFieldHiddenFlags(const char* name, + uint32_t expected_visibility, + const DexFile::ClassDef& class_def, + const DexFile& dex_file) { + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data"; + UNREACHABLE(); + } + + for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) { + if (it.IsAtMethod()) { + break; + } + const DexFile::FieldId& fid = dex_file.GetFieldId(it.GetMemberIndex()); + if (strcmp(name, dex_file.GetFieldName(fid)) == 0) { + uint32_t actual_visibility = it.GetFieldAccessFlags() & kAccVisibilityFlags; + if (actual_visibility != expected_visibility) { + LOG(FATAL) << "Field " << name << " in class " << dex_file.GetClassDescriptor(class_def) + << " does not have the expected visibility flags (" << expected_visibility + << " != " << actual_visibility << ")"; + UNREACHABLE(); + } + return it.DecodeHiddenAccessFlags(); + } + } + + LOG(FATAL) << "Could not find field " << name << " in class " + << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetMethodHiddenFlags(const char* name, + uint32_t expected_visibility, + bool expected_native, + const DexFile::ClassDef& class_def, + const DexFile& dex_file) { + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data"; + UNREACHABLE(); + } + + for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) { + if (!it.IsAtMethod()) { + continue; + } + const DexFile::MethodId& mid = dex_file.GetMethodId(it.GetMemberIndex()); + if (strcmp(name, dex_file.GetMethodName(mid)) == 0) { + if (expected_native != it.MemberIsNative()) { + LOG(FATAL) << "Expected native=" << expected_native << " for method " << name + << " in class " << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + uint32_t actual_visibility = it.GetMethodAccessFlags() & kAccVisibilityFlags; + if (actual_visibility != expected_visibility) { + LOG(FATAL) << "Method " << name << " in class " << dex_file.GetClassDescriptor(class_def) + << " does not have the expected visibility flags (" << expected_visibility + << " != " << actual_visibility << ")"; + UNREACHABLE(); + } + return it.DecodeHiddenAccessFlags(); + } + } + + LOG(FATAL) << "Could not find method " << name << " in class " + << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetIFieldHiddenFlags(const DexFile& dex_file) { + return GetFieldHiddenFlags("ifield", kAccPublic, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSFieldHiddenFlags(const DexFile& dex_file) { + return GetFieldHiddenFlags("sfield", kAccPrivate, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetIMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "imethod", 0, /* native */ false, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "smethod", kAccPublic, /* native */ false, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetINMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "inmethod", kAccPublic, /* native */ true, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSNMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "snmethod", kAccProtected, /* native */ true, FindClass("LMain;", dex_file), dex_file); + } +}; + +TEST_F(HiddenApiTest, InstanceFieldNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +} // namespace art diff --git a/tools/jfuzz/run_dex_fuzz_test.py b/tools/jfuzz/run_dex_fuzz_test.py index a25ef3fca5..203e03d678 100755 --- a/tools/jfuzz/run_dex_fuzz_test.py +++ b/tools/jfuzz/run_dex_fuzz_test.py @@ -106,14 +106,14 @@ class DexFuzzTester(object): self.RunDexFuzz() def CompileOnHost(self): - """Compiles Test.java into classes.dex using either javac/dx or jack. + """Compiles Test.java into classes.dex using either javac/dx,d8 or jack. Raises: FatalError: error when compilation fails """ if self._dexer == 'dx' or self._dexer == 'd8': dbg = '-g' if self._debug_info else '-g:none' - if RunCommand(['javac', dbg, 'Test.java'], + if RunCommand(['javac', '--release=8', dbg, 'Test.java'], out=None, err='jerr.txt', timeout=30) != RetCode.SUCCESS: print('Unexpected error while running javac') raise FatalError('Unexpected error while running javac') diff --git a/tools/jfuzz/run_jfuzz_test.py b/tools/jfuzz/run_jfuzz_test.py index 34180d993f..bddce32ad5 100755 --- a/tools/jfuzz/run_jfuzz_test.py +++ b/tools/jfuzz/run_jfuzz_test.py @@ -133,7 +133,7 @@ class TestRunnerWithHostCompilation(TestRunner): def CompileOnHost(self): if self._dexer == 'dx' or self._dexer == 'd8': dbg = '-g' if self._debug_info else '-g:none' - if RunCommand(['javac', dbg, 'Test.java'], + if RunCommand(['javac', '--release=8', dbg, 'Test.java'], out=None, err=None, timeout=30) == RetCode.SUCCESS: dx = 'dx' if self._dexer == 'dx' else 'd8-compat-dx' retc = RunCommand([dx, '--dex', '--output=classes.dex'] + glob('*.class'), |