summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--build/Android.gtest.mk8
-rw-r--r--compiler/driver/compiled_method_storage.cc11
-rw-r--r--compiler/linker/elf_builder.h20
-rw-r--r--compiler/optimizing/code_generator_arm64.cc6
-rw-r--r--compiler/optimizing/code_generator_arm_vixl.cc6
-rw-r--r--compiler/optimizing/code_generator_mips.cc6
-rw-r--r--compiler/optimizing/code_generator_mips64.cc6
-rw-r--r--compiler/optimizing/code_generator_x86.cc5
-rw-r--r--compiler/optimizing/code_generator_x86_64.cc5
-rw-r--r--compiler/optimizing/code_sinking.cc4
-rw-r--r--compiler/optimizing/dead_code_elimination.cc72
-rw-r--r--compiler/optimizing/dead_code_elimination.h1
-rw-r--r--compiler/optimizing/graph_checker.cc10
-rw-r--r--compiler/optimizing/inliner.cc53
-rw-r--r--compiler/optimizing/nodes.h13
-rw-r--r--compiler/optimizing/optimizing_compiler_stats.h1
-rw-r--r--dex2oat/linker/oat_writer.cc21
-rw-r--r--dexlayout/compact_dex_writer.cc81
-rw-r--r--dexlayout/compact_dex_writer.h50
-rw-r--r--dexlayout/dex_ir.cc88
-rw-r--r--dexlayout/dex_ir.h43
-rw-r--r--dexlayout/dex_writer.cc4
-rw-r--r--dexlayout/dex_writer.h1
-rw-r--r--dexlayout/dexlayout.cc13
-rw-r--r--dexlayout/dexlayout.h2
-rw-r--r--runtime/Android.bp11
-rw-r--r--runtime/arch/arm/quick_entrypoints_arm.S23
-rw-r--r--runtime/arch/arm64/quick_entrypoints_arm64.S5
-rw-r--r--runtime/arch/x86/quick_entrypoints_x86.S7
-rw-r--r--runtime/arch/x86_64/quick_entrypoints_x86_64.S10
-rw-r--r--runtime/class_loader_context_test.cc38
-rw-r--r--runtime/dex/art_dex_file_loader.cc15
-rw-r--r--runtime/dex/art_dex_file_loader.h2
-rw-r--r--runtime/dex/code_item_accessors-no_art-inl.h31
-rw-r--r--runtime/dex/code_item_accessors.h1
-rw-r--r--runtime/dex/code_item_accessors_test.cc20
-rw-r--r--runtime/dex/compact_dex_file.h161
-rw-r--r--runtime/dex/compact_dex_file_test.cc61
-rw-r--r--runtime/dex/dex_file-inl.h2
-rw-r--r--runtime/dex/dex_file.h13
-rw-r--r--runtime/dex/dex_file_loader.cc28
-rw-r--r--runtime/dex/dex_file_loader.h2
-rw-r--r--runtime/dex/dex_hidden_access_flags.h110
-rw-r--r--runtime/elf.h5
-rw-r--r--runtime/fault_handler.cc14
-rw-r--r--runtime/gc/collector/concurrent_copying.cc1
-rw-r--r--runtime/gc/collector/semi_space.cc1
-rw-r--r--runtime/gc/gc_cause.cc1
-rw-r--r--runtime/gc/gc_cause.h3
-rw-r--r--runtime/gc/heap.cc69
-rw-r--r--runtime/gc/heap.h29
-rw-r--r--runtime/leb128.h2
-rw-r--r--runtime/modifiers.h8
-rw-r--r--runtime/native/dalvik_system_VMStack.cc10
-rw-r--r--runtime/parsed_options.cc3
-rw-r--r--runtime/runtime.cc4
-rw-r--r--runtime/runtime.h2
-rw-r--r--runtime/runtime_options.def1
-rw-r--r--runtime/thread.cc193
-rw-r--r--runtime/thread.h3
-rw-r--r--runtime/utils.h14
-rw-r--r--runtime/vdex_file.h4
-rw-r--r--test/004-NativeAllocations/src-art/Main.java147
-rw-r--r--test/168-vmstack-annotated/expected.txt0
-rw-r--r--test/168-vmstack-annotated/info.txt1
-rw-r--r--test/168-vmstack-annotated/run18
-rw-r--r--test/168-vmstack-annotated/src/Main.java225
-rw-r--r--test/305-other-fault-handler/expected.txt2
-rw-r--r--test/305-other-fault-handler/fault_handler.cc102
-rw-r--r--test/305-other-fault-handler/info.txt3
-rw-r--r--test/305-other-fault-handler/src/Main.java25
-rw-r--r--test/672-checker-throw-method/expected.txt1
-rw-r--r--test/672-checker-throw-method/info.txt1
-rw-r--r--test/672-checker-throw-method/src/Main.java244
-rw-r--r--test/673-checker-throw-vmethod/expected.txt1
-rw-r--r--test/673-checker-throw-vmethod/info.txt1
-rw-r--r--test/673-checker-throw-vmethod/src/Main.java219
-rw-r--r--test/Android.bp1
-rw-r--r--test/HiddenApi/Main.java26
-rw-r--r--test/knownfailures.json10
-rw-r--r--tools/hiddenapi/Android.bp64
-rw-r--r--tools/hiddenapi/README.md54
-rw-r--r--tools/hiddenapi/hiddenapi.cc408
-rw-r--r--tools/hiddenapi/hiddenapi_test.cc601
-rwxr-xr-xtools/jfuzz/run_dex_fuzz_test.py4
-rwxr-xr-xtools/jfuzz/run_jfuzz_test.py2
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,
+ &registers_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'),