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/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.cc4
-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/leb128.h2
-rw-r--r--runtime/modifiers.h8
-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/utils.h14
-rw-r--r--runtime/vdex_file.h4
-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/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
64 files changed, 2520 insertions, 195 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/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 e2b84c5f2d..3d3b121190 100644
--- a/dexlayout/dexlayout.cc
+++ b/dexlayout/dexlayout.cc
@@ -1929,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(),
@@ -1936,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/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/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/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/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/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