diff options
89 files changed, 2781 insertions, 584 deletions
diff --git a/compiler/dex/dex_to_dex_compiler.h b/compiler/dex/dex_to_dex_compiler.h index abd048167c..2105a9ded4 100644 --- a/compiler/dex/dex_to_dex_compiler.h +++ b/compiler/dex/dex_to_dex_compiler.h @@ -23,8 +23,8 @@ #include "base/bit_vector.h" #include "dex/dex_file.h" +#include "dex/invoke_type.h" #include "handle.h" -#include "invoke_type.h" #include "method_reference.h" #include "quicken_info.h" diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index e81d97b0a8..02465d37ba 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -18,7 +18,7 @@ #define ART_COMPILER_OPTIMIZING_INLINER_H_ #include "dex/dex_file_types.h" -#include "invoke_type.h" +#include "dex/invoke_type.h" #include "jit/profile_compilation_info.h" #include "optimization.h" diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc index 6928b70df7..acb830e524 100644 --- a/compiler/optimizing/intrinsics.cc +++ b/compiler/optimizing/intrinsics.cc @@ -19,9 +19,9 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "class_linker.h" +#include "dex/invoke_type.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" -#include "invoke_type.h" #include "mirror/dex_cache-inl.h" #include "nodes.h" #include "scoped_thread_state_change-inl.h" diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc index aae94b227c..88326d321b 100644 --- a/compiler/optimizing/load_store_elimination.cc +++ b/compiler/optimizing/load_store_elimination.cc @@ -25,51 +25,6 @@ #include <iostream> -/** - * The general algorithm of load-store elimination (LSE). - * Load-store analysis in the previous pass collects a list of heap locations - * and does alias analysis of those heap locations. - * LSE keeps track of a list of heap values corresponding to the heap - * locations. It visits basic blocks in reverse post order and for - * each basic block, visits instructions sequentially, and processes - * instructions as follows: - * - If the instruction is a load, and the heap location for that load has a - * valid heap value, the load can be eliminated. In order to maintain the - * validity of all heap locations during the optimization phase, the real - * elimination is delayed till the end of LSE. - * - If the instruction is a store, it updates the heap value for the heap - * location of the store with the store instruction. The real heap value - * can be fetched from the store instruction. Heap values are invalidated - * for heap locations that may alias with the store instruction's heap - * location. The store instruction can be eliminated unless the value stored - * is later needed e.g. by a load from the same/aliased heap location or - * the heap location persists at method return/deoptimization. - * The store instruction is also needed if it's not used to track the heap - * value anymore, e.g. when it fails to merge with the heap values from other - * predecessors. - * - A store that stores the same value as the heap value is eliminated. - * - The list of heap values are merged at basic block entry from the basic - * block's predecessors. The algorithm is single-pass, so loop side-effects is - * used as best effort to decide if a heap location is stored inside the loop. - * - A special type of objects called singletons are instantiated in the method - * and have a single name, i.e. no aliases. Singletons have exclusive heap - * locations since they have no aliases. Singletons are helpful in narrowing - * down the life span of a heap location such that they do not always - * need to participate in merging heap values. Allocation of a singleton - * can be eliminated if that singleton is not used and does not persist - * at method return/deoptimization. - * - For newly instantiated instances, their heap values are initialized to - * language defined default values. - * - Some instructions such as invokes are treated as loading and invalidating - * all the heap values, depending on the instruction's side effects. - * - Finalizable objects are considered as persisting at method - * return/deoptimization. - * - Currently this LSE algorithm doesn't handle SIMD graph, e.g. with VecLoad - * and VecStore instructions. - * - Currently this LSE algorithm doesn't handle graph with try-catch, due to - * the special block merging structure. - */ - namespace art { // An unknown heap value. Loads with such a value in the heap location cannot be eliminated. @@ -104,7 +59,8 @@ class LSEVisitor : public HGraphDelegateVisitor { removed_loads_(allocator_.Adapter(kArenaAllocLSE)), substitute_instructions_for_loads_(allocator_.Adapter(kArenaAllocLSE)), possibly_removed_stores_(allocator_.Adapter(kArenaAllocLSE)), - singleton_new_instances_(allocator_.Adapter(kArenaAllocLSE)) { + singleton_new_instances_(allocator_.Adapter(kArenaAllocLSE)), + singleton_new_arrays_(allocator_.Adapter(kArenaAllocLSE)) { } void VisitBasicBlock(HBasicBlock* block) OVERRIDE { @@ -132,26 +88,19 @@ class LSEVisitor : public HGraphDelegateVisitor { return type_conversion; } - // Find an instruction's substitute if it's a removed load. + // Find an instruction's substitute if it should be removed. // Return the same instruction if it should not be removed. HInstruction* FindSubstitute(HInstruction* instruction) { - if (!IsLoad(instruction)) { - return instruction; - } size_t size = removed_loads_.size(); for (size_t i = 0; i < size; i++) { if (removed_loads_[i] == instruction) { - HInstruction* substitute = substitute_instructions_for_loads_[i]; - // The substitute list is a flat hierarchy. - DCHECK_EQ(FindSubstitute(substitute), substitute); - return substitute; + return substitute_instructions_for_loads_[i]; } } return instruction; } void AddRemovedLoad(HInstruction* load, HInstruction* heap_value) { - DCHECK(IsLoad(load)); DCHECK_EQ(FindSubstitute(heap_value), heap_value) << "Unexpected heap_value that has a substitute " << heap_value->DebugName(); removed_loads_.push_back(load); @@ -258,59 +207,28 @@ class LSEVisitor : public HGraphDelegateVisitor { new_instance->GetBlock()->RemoveInstruction(new_instance); } } - } - - private: - static bool IsLoad(HInstruction* instruction) { - if (instruction == kUnknownHeapValue || instruction == kDefaultHeapValue) { - return false; - } - // Unresolved load is not treated as a load. - return instruction->IsInstanceFieldGet() || - instruction->IsStaticFieldGet() || - instruction->IsArrayGet(); - } - - static bool IsStore(HInstruction* instruction) { - if (instruction == kUnknownHeapValue || instruction == kDefaultHeapValue) { - return false; - } - // Unresolved store is not treated as a store. - return instruction->IsInstanceFieldSet() || - instruction->IsArraySet() || - instruction->IsStaticFieldSet(); - } - - // Returns the real heap value by finding its substitute or by "peeling" - // a store instruction. - HInstruction* GetRealHeapValue(HInstruction* heap_value) { - if (IsLoad(heap_value)) { - return FindSubstitute(heap_value); - } - if (!IsStore(heap_value)) { - return heap_value; - } + for (HInstruction* new_array : singleton_new_arrays_) { + size_t removed = HConstructorFence::RemoveConstructorFences(new_array); + MaybeRecordStat(stats_, + MethodCompilationStat::kConstructorFenceRemovedLSE, + removed); - // We keep track of store instructions as the heap values which might be - // eliminated if the stores are later found not necessary. The real stored - // value needs to be fetched from the store instruction. - if (heap_value->IsInstanceFieldSet()) { - heap_value = heap_value->AsInstanceFieldSet()->GetValue(); - } else if (heap_value->IsStaticFieldSet()) { - heap_value = heap_value->AsStaticFieldSet()->GetValue(); - } else { - DCHECK(heap_value->IsArraySet()); - heap_value = heap_value->AsArraySet()->GetValue(); + if (!new_array->HasNonEnvironmentUses()) { + new_array->RemoveEnvironmentUsers(); + new_array->GetBlock()->RemoveInstruction(new_array); + } } - // heap_value may already be a removed load. - return FindSubstitute(heap_value); } - // If heap_value is a store, need to keep the store. - // This is necessary if a heap value is killed or replaced by another value, - // so that the store is no longer used to track heap value. + private: + // If heap_values[index] is an instance field store, need to keep the store. + // This is necessary if a heap value is killed due to merging, or loop side + // effects (which is essentially merging also), since a load later from the + // location won't be eliminated. void KeepIfIsStore(HInstruction* heap_value) { - if (!IsStore(heap_value)) { + if (heap_value == kDefaultHeapValue || + heap_value == kUnknownHeapValue || + !(heap_value->IsInstanceFieldSet() || heap_value->IsArraySet())) { return; } auto idx = std::find(possibly_removed_stores_.begin(), @@ -321,41 +239,26 @@ class LSEVisitor : public HGraphDelegateVisitor { } } - // If a heap location X may alias with heap location at `loc_index` - // and heap_values of that heap location X holds a store, keep that store. - // It's needed for a dependent load that's not eliminated since any store - // that may put value into the load's heap location needs to be kept. - void KeepStoresIfAliasedToLocation(ScopedArenaVector<HInstruction*>& heap_values, - size_t loc_index) { - for (size_t i = 0; i < heap_values.size(); i++) { - if ((i == loc_index) || heap_location_collector_.MayAlias(i, loc_index)) { - KeepIfIsStore(heap_values[i]); - } - } - } - void HandleLoopSideEffects(HBasicBlock* block) { DCHECK(block->IsLoopHeader()); int block_id = block->GetBlockId(); ScopedArenaVector<HInstruction*>& heap_values = heap_values_for_[block_id]; - HBasicBlock* pre_header = block->GetLoopInformation()->GetPreHeader(); - ScopedArenaVector<HInstruction*>& pre_header_heap_values = - heap_values_for_[pre_header->GetBlockId()]; - // Don't eliminate loads in irreducible loops. - // Also keep the stores before the loop. + // Don't eliminate loads in irreducible loops. This is safe for singletons, because + // they are always used by the non-eliminated loop-phi. if (block->GetLoopInformation()->IsIrreducible()) { if (kIsDebugBuild) { for (size_t i = 0; i < heap_values.size(); i++) { DCHECK_EQ(heap_values[i], kUnknownHeapValue); } } - for (size_t i = 0; i < heap_values.size(); i++) { - KeepIfIsStore(pre_header_heap_values[i]); - } return; } + HBasicBlock* pre_header = block->GetLoopInformation()->GetPreHeader(); + ScopedArenaVector<HInstruction*>& pre_header_heap_values = + heap_values_for_[pre_header->GetBlockId()]; + // Inherit the values from pre-header. for (size_t i = 0; i < heap_values.size(); i++) { heap_values[i] = pre_header_heap_values[i]; @@ -367,17 +270,18 @@ class LSEVisitor : public HGraphDelegateVisitor { for (size_t i = 0; i < heap_values.size(); i++) { HeapLocation* location = heap_location_collector_.GetHeapLocation(i); ReferenceInfo* ref_info = location->GetReferenceInfo(); - if (ref_info->IsSingleton() && !location->IsValueKilledByLoopSideEffects()) { - // A singleton's field that's not stored into inside a loop is + if (ref_info->IsSingletonAndRemovable() && + !location->IsValueKilledByLoopSideEffects()) { + // A removable singleton's field that's not stored into inside a loop is // invariant throughout the loop. Nothing to do. } else { - // heap value is killed by loop side effects. + // heap value is killed by loop side effects (stored into directly, or + // due to aliasing). Or the heap value may be needed after method return + // or deoptimization. KeepIfIsStore(pre_header_heap_values[i]); heap_values[i] = kUnknownHeapValue; } } - } else { - // The loop doesn't kill any value. } } @@ -396,73 +300,45 @@ class LSEVisitor : public HGraphDelegateVisitor { ScopedArenaVector<HInstruction*>& heap_values = heap_values_for_[block->GetBlockId()]; for (size_t i = 0; i < heap_values.size(); i++) { HInstruction* merged_value = nullptr; - // If we can merge the store itself from the predecessors, we keep - // the store as the heap value as long as possible. In case we cannot - // merge the store, we try to merge the values of the stores. - HInstruction* merged_store_value = nullptr; // Whether merged_value is a result that's merged from all predecessors. bool from_all_predecessors = true; ReferenceInfo* ref_info = heap_location_collector_.GetHeapLocation(i)->GetReferenceInfo(); - HInstruction* ref = ref_info->GetReference(); HInstruction* singleton_ref = nullptr; if (ref_info->IsSingleton()) { - // We do more analysis based on singleton's liveness when merging - // heap values for such cases. - singleton_ref = ref; + // We do more analysis of liveness when merging heap values for such + // cases since stores into such references may potentially be eliminated. + singleton_ref = ref_info->GetReference(); } for (HBasicBlock* predecessor : predecessors) { HInstruction* pred_value = heap_values_for_[predecessor->GetBlockId()][i]; - if (!IsStore(pred_value)) { - pred_value = FindSubstitute(pred_value); - } - DCHECK(pred_value != nullptr); - HInstruction* pred_store_value = GetRealHeapValue(pred_value); if ((singleton_ref != nullptr) && !singleton_ref->GetBlock()->Dominates(predecessor)) { - // singleton_ref is not live in this predecessor. No need to merge - // since singleton_ref is not live at the beginning of this block. + // singleton_ref is not live in this predecessor. Skip this predecessor since + // it does not really have the location. DCHECK_EQ(pred_value, kUnknownHeapValue); from_all_predecessors = false; - break; + continue; } if (merged_value == nullptr) { // First seen heap value. - DCHECK(pred_value != nullptr); merged_value = pred_value; } else if (pred_value != merged_value) { // There are conflicting values. merged_value = kUnknownHeapValue; - // We may still be able to merge store values. - } - - // Conflicting stores may be storing the same value. We do another merge - // of real stored values. - if (merged_store_value == nullptr) { - // First seen store value. - DCHECK(pred_store_value != nullptr); - merged_store_value = pred_store_value; - } else if (pred_store_value != merged_store_value) { - // There are conflicting store values. - merged_store_value = kUnknownHeapValue; - // There must be conflicting stores also. - DCHECK_EQ(merged_value, kUnknownHeapValue); - // No need to merge anymore. break; } } - if (merged_value == nullptr) { - DCHECK(!from_all_predecessors); - DCHECK(singleton_ref != nullptr); - } - if (from_all_predecessors) { - if (ref_info->IsSingletonAndRemovable() && - block->IsSingleReturnOrReturnVoidAllowingPhis()) { - // Values in the singleton are not needed anymore. - } else if (!IsStore(merged_value)) { - // We don't track merged value as a store anymore. We have to - // hold the stores in predecessors live here. + if (ref_info->IsSingleton()) { + if (ref_info->IsSingletonAndNonRemovable() || + (merged_value == kUnknownHeapValue && + !block->IsSingleReturnOrReturnVoidAllowingPhis())) { + // The heap value may be needed after method return or deoptimization, + // or there are conflicting heap values from different predecessors and + // this block is not a single return, + // keep the last store in each predecessor since future loads may not + // be eliminated. for (HBasicBlock* predecessor : predecessors) { ScopedArenaVector<HInstruction*>& pred_values = heap_values_for_[predecessor->GetBlockId()]; @@ -470,33 +346,18 @@ class LSEVisitor : public HGraphDelegateVisitor { } } } else { - DCHECK(singleton_ref != nullptr); - // singleton_ref is non-existing at the beginning of the block. There is - // no need to keep the stores. + // Currenctly we don't eliminate stores to non-singletons. } - if (!from_all_predecessors) { + if ((merged_value == nullptr) || !from_all_predecessors) { DCHECK(singleton_ref != nullptr); DCHECK((singleton_ref->GetBlock() == block) || - !singleton_ref->GetBlock()->Dominates(block)) - << "method: " << GetGraph()->GetMethodName(); + !singleton_ref->GetBlock()->Dominates(block)); // singleton_ref is not defined before block or defined only in some of its // predecessors, so block doesn't really have the location at its entry. heap_values[i] = kUnknownHeapValue; - } else if (predecessors.size() == 1) { - // Inherit heap value from the single predecessor. - DCHECK_EQ(heap_values_for_[predecessors[0]->GetBlockId()][i], merged_value); - heap_values[i] = merged_value; } else { - DCHECK(merged_value == kUnknownHeapValue || - merged_value == kDefaultHeapValue || - merged_value->GetBlock()->Dominates(block)); - if (merged_value != kUnknownHeapValue) { - heap_values[i] = merged_value; - } else { - // Stores in different predecessors may be storing the same value. - heap_values[i] = merged_store_value; - } + heap_values[i] = merged_value; } } } @@ -562,12 +423,23 @@ class LSEVisitor : public HGraphDelegateVisitor { heap_values[idx] = constant; return; } - heap_value = GetRealHeapValue(heap_value); + if (heap_value != kUnknownHeapValue) { + if (heap_value->IsInstanceFieldSet() || heap_value->IsArraySet()) { + HInstruction* store = heap_value; + // This load must be from a singleton since it's from the same + // field/element that a "removed" store puts the value. That store + // must be to a singleton's field/element. + DCHECK(ref_info->IsSingleton()); + // Get the real heap value of the store. + heap_value = heap_value->IsInstanceFieldSet() ? store->InputAt(1) : store->InputAt(2); + // heap_value may already have a substitute. + heap_value = FindSubstitute(heap_value); + } + } if (heap_value == kUnknownHeapValue) { // Load isn't eliminated. Put the load as the value into the HeapLocation. // This acts like GVN but with better aliasing analysis. heap_values[idx] = instruction; - KeepStoresIfAliasedToLocation(heap_values, idx); } else { if (DataType::Kind(heap_value->GetType()) != DataType::Kind(instruction->GetType())) { // The only situation where the same heap location has different type is when @@ -580,10 +452,6 @@ class LSEVisitor : public HGraphDelegateVisitor { DCHECK(heap_value->IsArrayGet()) << heap_value->DebugName(); DCHECK(instruction->IsArrayGet()) << instruction->DebugName(); } - // Load isn't eliminated. Put the load as the value into the HeapLocation. - // This acts like GVN but with better aliasing analysis. - heap_values[idx] = instruction; - KeepStoresIfAliasedToLocation(heap_values, idx); return; } AddRemovedLoad(instruction, heap_value); @@ -592,21 +460,12 @@ class LSEVisitor : public HGraphDelegateVisitor { } bool Equal(HInstruction* heap_value, HInstruction* value) { - DCHECK(!IsStore(value)) << value->DebugName(); - if (heap_value == kUnknownHeapValue) { - // Don't compare kUnknownHeapValue with other values. - return false; - } if (heap_value == value) { return true; } if (heap_value == kDefaultHeapValue && GetDefaultValue(value->GetType()) == value) { return true; } - HInstruction* real_heap_value = GetRealHeapValue(heap_value); - if (real_heap_value != heap_value) { - return Equal(real_heap_value, value); - } return false; } @@ -617,7 +476,6 @@ class LSEVisitor : public HGraphDelegateVisitor { size_t vector_length, int16_t declaring_class_def_index, HInstruction* value) { - DCHECK(!IsStore(value)) << value->DebugName(); // value may already have a substitute. value = FindSubstitute(value); HInstruction* original_ref = heap_location_collector_.HuntForOriginalReference(ref); @@ -628,47 +486,59 @@ class LSEVisitor : public HGraphDelegateVisitor { ScopedArenaVector<HInstruction*>& heap_values = heap_values_for_[instruction->GetBlock()->GetBlockId()]; HInstruction* heap_value = heap_values[idx]; + bool same_value = false; bool possibly_redundant = false; - if (Equal(heap_value, value)) { // Store into the heap location with the same value. - // This store can be eliminated right away. - instruction->GetBlock()->RemoveInstruction(instruction); - return; - } else { + same_value = true; + } else if (index != nullptr && + heap_location_collector_.GetHeapLocation(idx)->HasAliasedLocations()) { + // For array element, don't eliminate stores if the location can be aliased + // (due to either ref or index aliasing). + } else if (ref_info->IsSingleton()) { + // Store into a field/element of a singleton. The value cannot be killed due to + // aliasing/invocation. It can be redundant since future loads can + // directly get the value set by this instruction. The value can still be killed due to + // merging or loop side effects. Stores whose values are killed due to merging/loop side + // effects later will be removed from possibly_removed_stores_ when that is detected. + // Stores whose values may be needed after method return or deoptimization + // are also removed from possibly_removed_stores_ when that is detected. + possibly_redundant = true; HLoopInformation* loop_info = instruction->GetBlock()->GetLoopInformation(); - if (loop_info == nullptr) { - // Store is not in a loop. We try to precisely track the heap value by - // the store. - possibly_redundant = true; - } else if (!loop_info->IsIrreducible()) { - // instruction is a store in the loop so the loop must do write. + if (loop_info != nullptr) { + // instruction is a store in the loop so the loop must does write. DCHECK(side_effects_.GetLoopEffects(loop_info->GetHeader()).DoesAnyWrite()); - if (ref_info->IsSingleton() && !loop_info->IsDefinedOutOfTheLoop(original_ref)) { - // original_ref is created inside the loop. Value stored to it isn't needed at - // the loop header. This is true for outer loops also. - possibly_redundant = true; - } else { + + if (loop_info->IsDefinedOutOfTheLoop(original_ref)) { + DCHECK(original_ref->GetBlock()->Dominates(loop_info->GetPreHeader())); // Keep the store since its value may be needed at the loop header. + possibly_redundant = false; + } else { + // The singleton is created inside the loop. Value stored to it isn't needed at + // the loop header. This is true for outer loops also. } - } else { - // Keep the store inside irreducible loops. } } - if (possibly_redundant) { + if (same_value || possibly_redundant) { possibly_removed_stores_.push_back(instruction); } - // Put the store as the heap value. If the value is loaded or needed after - // return/deoptimization later, this store isn't really redundant. - heap_values[idx] = instruction; - + if (!same_value) { + if (possibly_redundant) { + DCHECK(instruction->IsInstanceFieldSet() || instruction->IsArraySet()); + // Put the store as the heap value. If the value is loaded from heap + // by a load later, this store isn't really redundant. + heap_values[idx] = instruction; + } else { + heap_values[idx] = value; + } + } // This store may kill values in other heap locations due to aliasing. for (size_t i = 0; i < heap_values.size(); i++) { if (i == idx) { continue; } - if (Equal(heap_values[i], value)) { + if (heap_values[i] == value) { // Same value should be kept even if aliasing happens. continue; } @@ -677,9 +547,7 @@ class LSEVisitor : public HGraphDelegateVisitor { continue; } if (heap_location_collector_.MayAlias(i, idx)) { - // Kill heap locations that may alias and as a result if the heap value - // is a store, the store needs to be kept. - KeepIfIsStore(heap_values[i]); + // Kill heap locations that may alias. heap_values[i] = kUnknownHeapValue; } } @@ -765,35 +633,24 @@ class LSEVisitor : public HGraphDelegateVisitor { const ScopedArenaVector<HInstruction*>& heap_values = heap_values_for_[instruction->GetBlock()->GetBlockId()]; for (HInstruction* heap_value : heap_values) { + // Filter out fake instructions before checking instruction kind below. + if (heap_value == kUnknownHeapValue || heap_value == kDefaultHeapValue) { + continue; + } // A store is kept as the heap value for possibly removed stores. - // That value stored is generally observeable after deoptimization, except - // for singletons that don't escape after deoptimization. - if (IsStore(heap_value)) { - if (heap_value->IsStaticFieldSet()) { - KeepIfIsStore(heap_value); - continue; - } + if (heap_value->IsInstanceFieldSet() || heap_value->IsArraySet()) { + // Check whether the reference for a store is used by an environment local of + // HDeoptimize. HInstruction* reference = heap_value->InputAt(0); - if (heap_location_collector_.FindReferenceInfoOf(reference)->IsSingleton()) { - if (reference->IsNewInstance() && reference->AsNewInstance()->IsFinalizable()) { - // Finalizable objects alway escape. + DCHECK(heap_location_collector_.FindReferenceInfoOf(reference)->IsSingleton()); + for (const HUseListNode<HEnvironment*>& use : reference->GetEnvUses()) { + HEnvironment* user = use.GetUser(); + if (user->GetHolder() == instruction) { + // The singleton for the store is visible at this deoptimization + // point. Need to keep the store so that the heap value is + // seen by the interpreter. KeepIfIsStore(heap_value); - continue; } - // Check whether the reference for a store is used by an environment local of - // HDeoptimize. If not, the singleton is not observed after - // deoptimizion. - for (const HUseListNode<HEnvironment*>& use : reference->GetEnvUses()) { - HEnvironment* user = use.GetUser(); - if (user->GetHolder() == instruction) { - // The singleton for the store is visible at this deoptimization - // point. Need to keep the store so that the heap value is - // seen by the interpreter. - KeepIfIsStore(heap_value); - } - } - } else { - KeepIfIsStore(heap_value); } } } @@ -901,7 +758,7 @@ class LSEVisitor : public HGraphDelegateVisitor { return; } if (ref_info->IsSingletonAndRemovable()) { - singleton_new_instances_.push_back(new_array); + singleton_new_arrays_.push_back(new_array); } ScopedArenaVector<HInstruction*>& heap_values = heap_values_for_[new_array->GetBlock()->GetBlockId()]; @@ -934,6 +791,7 @@ class LSEVisitor : public HGraphDelegateVisitor { ScopedArenaVector<HInstruction*> possibly_removed_stores_; ScopedArenaVector<HInstruction*> singleton_new_instances_; + ScopedArenaVector<HInstruction*> singleton_new_arrays_; DISALLOW_COPY_AND_ASSIGN(LSEVisitor); }; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index b0657d6f1c..a9782a6afd 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -32,11 +32,11 @@ #include "deoptimization_kind.h" #include "dex/dex_file.h" #include "dex/dex_file_types.h" +#include "dex/invoke_type.h" #include "entrypoints/quick/quick_entrypoints_enum.h" #include "handle.h" #include "handle_scope.h" #include "intrinsics_enum.h" -#include "invoke_type.h" #include "locations.h" #include "method_reference.h" #include "mirror/class.h" diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index a836d75a72..8555abf9fd 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -2404,7 +2404,7 @@ class Dex2Oat FINAL { bool AddDexFileSources() { TimingLogger::ScopedTiming t2("AddDexFileSources", timings_); - if (input_vdex_file_ != nullptr) { + if (input_vdex_file_ != nullptr && input_vdex_file_->HasDexSection()) { DCHECK_EQ(oat_writers_.size(), 1u); const std::string& name = zip_location_.empty() ? dex_locations_[0] : zip_location_; DCHECK(!name.empty()); diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index 798e3f6d1c..6fcf6952e8 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -1136,7 +1136,7 @@ TEST_F(Dex2oatClassLoaderContextTest, ContextWithStrippedDexFilesBackedByOdex) { std::string expected_classpath_key; { // Open the oat file to get the expected classpath. - OatFileAssistant oat_file_assistant(stripped_classpath.c_str(), kRuntimeISA, false); + OatFileAssistant oat_file_assistant(stripped_classpath.c_str(), kRuntimeISA, false, false); std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile()); std::vector<std::unique_ptr<const DexFile>> oat_dex_files = OatFileAssistant::LoadDexFiles(*oat_file, stripped_classpath.c_str()); diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc index bedc4576d5..6d4b3e3e52 100644 --- a/dexoptanalyzer/dexoptanalyzer.cc +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -260,6 +260,7 @@ class DexoptAnalyzer FINAL { oat_file_assistant = std::make_unique<OatFileAssistant>(dex_file_.c_str(), isa_, false /*load_executable*/, + false /*only_load_system_executable*/, vdex_fd_, oat_fd_, zip_fd_); diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc index 53d84836fc..9e11a25e58 100644 --- a/openjdkjvmti/deopt_manager.cc +++ b/openjdkjvmti/deopt_manager.cc @@ -38,11 +38,11 @@ #include "base/enums.h" #include "base/mutex-inl.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc index fa7a34401d..d5fffdf439 100644 --- a/openjdkjvmti/ti_breakpoint.cc +++ b/openjdkjvmti/ti_breakpoint.cc @@ -39,11 +39,11 @@ #include "base/mutex-inl.h" #include "deopt_manager.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" diff --git a/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc index db5c31c43d..c016966d21 100644 --- a/openjdkjvmti/ti_field.cc +++ b/openjdkjvmti/ti_field.cc @@ -35,9 +35,9 @@ #include "art_jvmti.h" #include "base/enums.h" #include "dex/dex_file_annotations.h" +#include "dex/modifiers.h" #include "jni_internal.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 57fb699435..3f144c8f0f 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -40,6 +40,7 @@ #include "dex/code_item_accessors-inl.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "events-inl.h" #include "jit/jit.h" #include "jni_internal.h" @@ -47,7 +48,6 @@ #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" -#include "modifiers.h" #include "nativehelper/scoped_local_ref.h" #include "oat_file.h" #include "runtime_callbacks.h" diff --git a/profman/profman.cc b/profman/profman.cc index 9f3e3b6ac5..ffc3c0170f 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -149,6 +149,10 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(" --boot-image-sampled-method-threshold=<value>: minimum number of profiles a"); UsageError(" non-hot method needs to be in order to be hot in the output profile. The"); UsageError(" default is max int."); + UsageError(" --copy-and-update-profile-key: if present, profman will copy the profile from"); + UsageError(" the file passed with --profile-fd(file) to the profile passed with"); + UsageError(" --reference-profile-fd(file) and update at the same time the profile-key"); + UsageError(" of entries corresponding to the apks passed with --apk(-fd)."); UsageError(""); exit(EXIT_FAILURE); @@ -186,7 +190,8 @@ class ProfMan FINAL { test_profile_method_percerntage_(kDefaultTestProfileMethodPercentage), test_profile_class_percentage_(kDefaultTestProfileClassPercentage), test_profile_seed_(NanoTime()), - start_ns_(NanoTime()) {} + start_ns_(NanoTime()), + copy_and_update_profile_key_(false) {} ~ProfMan() { LogCompletionTime(); @@ -302,11 +307,13 @@ class ProfMan FINAL { "should only be used together"); } ProfileAssistant::ProcessingResult result; + if (profile_files_.empty()) { // The file doesn't need to be flushed here (ProcessProfiles will do it) // so don't check the usage. File file(reference_profile_file_fd_, false); - result = ProfileAssistant::ProcessProfiles(profile_files_fd_, reference_profile_file_fd_); + result = ProfileAssistant::ProcessProfiles(profile_files_fd_, + reference_profile_file_fd_); CloseAllFds(profile_files_fd_, "profile_files_fd_"); } else { result = ProfileAssistant::ProcessProfiles(profile_files_, reference_profile_file_); @@ -314,7 +321,7 @@ class ProfMan FINAL { return result; } - void OpenApkFilesFromLocations(std::vector<std::unique_ptr<const DexFile>>* dex_files) { + void OpenApkFilesFromLocations(std::vector<std::unique_ptr<const DexFile>>* dex_files) const { bool use_apk_fd_list = !apks_fd_.empty(); if (use_apk_fd_list) { // Get the APKs from the collection of FDs. @@ -1070,6 +1077,42 @@ class ProfMan FINAL { return !test_profile_.empty(); } + bool ShouldCopyAndUpdateProfileKey() const { + return copy_and_update_profile_key_; + } + + bool CopyAndUpdateProfileKey() const { + // Validate that at least one profile file was passed, as well as a reference profile. + if (!(profile_files_.size() == 1 ^ profile_files_fd_.size() == 1)) { + Usage("Only one profile file should be specified."); + } + if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) { + Usage("No reference profile file specified."); + } + + if (apk_files_.empty() && apks_fd_.empty()) { + Usage("No apk files specified"); + } + + bool use_fds = profile_files_fd_.size() == 1; + + ProfileCompilationInfo profile; + // Do not clear if invalid. The input might be an archive. + if (profile.Load(profile_files_[0], /*clear_if_invalid*/ false)) { + // Open the dex files to look up classes and methods. + std::vector<std::unique_ptr<const DexFile>> dex_files; + OpenApkFilesFromLocations(&dex_files); + if (!profile.UpdateProfileKeys(dex_files)) { + return false; + } + return use_fds + ? profile.Save(reference_profile_file_fd_) + : profile.Save(reference_profile_file_, /*bytes_written*/ nullptr); + } else { + return false; + } + } + private: static void ParseFdForCollection(const StringPiece& option, const char* arg_name, @@ -1114,6 +1157,7 @@ class ProfMan FINAL { uint16_t test_profile_class_percentage_; uint32_t test_profile_seed_; uint64_t start_ns_; + bool copy_and_update_profile_key_; }; // See ProfileAssistant::ProcessingResult for return codes. diff --git a/runtime/Android.bp b/runtime/Android.bp index aba2b0e2a0..f2f7c3e3d0 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -14,12 +14,6 @@ // limitations under the License. // -// Keep the __jit_debug_register_code symbol as a unique symbol during ICF for architectures where -// we use gold as the linker (arm, x86, x86_64). The symbol is used by the debuggers to detect when -// new jit code is generated. We don't want it to be called when a different function with the same -// (empty) body is called. -JIT_DEBUG_REGISTER_CODE_LDFLAGS = ["-Wl,--keep-unique,__jit_debug_register_code"] - cc_defaults { name: "libdexfile_defaults", defaults: ["art_defaults"], @@ -33,6 +27,7 @@ cc_defaults { "dex/dex_file_tracking_registrar.cc", "dex/dex_file_verifier.cc", "dex/dex_instruction.cc", + "dex/modifiers.cc", "dex/standard_dex_file.cc", "utf.cc", "utils.cc", @@ -56,16 +51,8 @@ cc_defaults { ], }, }, - header_libs: [ - "jni_headers", - ], generated_sources: ["art_operator_srcs"], - // asm_support_gen.h (used by asm_support.h) is generated with cpp-define-generator - generated_headers: ["cpp-define-generator-asm-support"], - // export our headers so the libart-gtest targets can use it as well. - export_generated_headers: ["cpp-define-generator-asm-support"], include_dirs: [ - "external/icu/icu4c/source/common", "external/zlib", ], shared_libs: [ @@ -78,11 +65,20 @@ cc_defaults { // Exporting "." would shadow the system elf.h with our elf.h, // which in turn breaks any tools that reference this library. // export_include_dirs: ["."], +} - // ART's macros.h depends on libbase's macros.h. - // Note: runtime_options.h depends on cmdline. But we don't really want to export this - // generically. dex2oat takes care of it itself. - export_shared_lib_headers: ["libbase"], +gensrcs { + name: "dexfile_operator_srcs", + cmd: "$(location generate-operator-out.py) art/runtime $(in) > $(out)", + tool_files: ["generate-operator-out.py"], + srcs: [ + "dex/dex_file.h", + "dex/dex_file_layout.h", + "dex/dex_instruction.h", + "dex/dex_instruction_utils.h", + "dex/invoke_type.h", + ], + output_extension: "operator_out.cc", } art_cc_library { @@ -95,6 +91,12 @@ art_cc_library { }, } +// Keep the __jit_debug_register_code symbol as a unique symbol during ICF for architectures where +// we use gold as the linker (arm, x86, x86_64). The symbol is used by the debuggers to detect when +// new jit code is generated. We don't want it to be called when a different function with the same +// (empty) body is called. +JIT_DEBUG_REGISTER_CODE_LDFLAGS = ["-Wl,--keep-unique,__jit_debug_register_code"] + cc_defaults { name: "libart_defaults", defaults: ["art_defaults"], @@ -142,6 +144,7 @@ cc_defaults { "dex/dex_file_tracking_registrar.cc", "dex/dex_file_verifier.cc", "dex/dex_instruction.cc", + "dex/modifiers.cc", "dex/standard_dex_file.cc", "dex_to_dex_decompiler.cc", "elf_file.cc", @@ -535,6 +538,7 @@ gensrcs { "dex/dex_file_layout.h", "dex/dex_instruction.h", "dex/dex_instruction_utils.h", + "dex/invoke_type.h", "gc_root.h", "gc/allocator_type.h", "gc/allocator/rosalloc.h", @@ -547,7 +551,6 @@ gensrcs { "image.h", "instrumentation.h", "indirect_reference_table.h", - "invoke_type.h", "jdwp_provider.h", "jdwp/jdwp.h", "jdwp/jdwp_constants.h", diff --git a/runtime/art_field.h b/runtime/art_field.h index 46b013da7e..0eeeef2f2f 100644 --- a/runtime/art_field.h +++ b/runtime/art_field.h @@ -20,8 +20,8 @@ #include <jni.h> #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "offsets.h" #include "primitive.h" diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index c9a77331a7..65bacd8237 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -27,8 +27,9 @@ #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/invoke_type.h" #include "gc_root-inl.h" -#include "invoke_type.h" +#include "intrinsics_enum.h" #include "jit/profiling_info.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" @@ -412,7 +413,11 @@ inline void ArtMethod::SetIntrinsic(uint32_t intrinsic) { DCHECK_EQ(is_default_conflict, IsDefaultConflicting()); DCHECK_EQ(is_compilable, IsCompilable()); DCHECK_EQ(must_count_locks, MustCountLocks()); - DCHECK_EQ(hidden_api_list, GetHiddenApiAccessFlags()); + // We need to special case java.lang.ref.Reference.getRefererent. The Java method + // is hidden but we do not yet have a way of making intrinsics hidden. + if (intrinsic != static_cast<uint32_t>(Intrinsics::kReferenceGetReferent)) { + DCHECK_EQ(hidden_api_list, GetHiddenApiAccessFlags()); + } } else { SetAccessFlags(new_value); } diff --git a/runtime/art_method.h b/runtime/art_method.h index 4501450e05..ce8e8ac612 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -31,8 +31,8 @@ #include "dex/code_item_accessors.h" #include "dex/dex_file.h" #include "dex/dex_instruction_iterator.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "offsets.h" #include "primitive.h" diff --git a/runtime/base/file_utils.cc b/runtime/base/file_utils.cc index 63b4ac56d0..d22fd994ee 100644 --- a/runtime/base/file_utils.cc +++ b/runtime/base/file_utils.cc @@ -353,4 +353,9 @@ int MadviseLargestPageAlignedRegion(const uint8_t* begin, const uint8_t* end, in return 0; } +bool LocationIsOnSystem(const char* location) { + UniqueCPtr<const char[]> path(realpath(location, nullptr)); + return path != nullptr && android::base::StartsWith(path.get(), GetAndroidRoot().c_str()); +} + } // namespace art diff --git a/runtime/base/file_utils.h b/runtime/base/file_utils.h index e4555ad3cb..cac0950d9c 100644 --- a/runtime/base/file_utils.h +++ b/runtime/base/file_utils.h @@ -82,6 +82,9 @@ int64_t GetFileSizeBytes(const std::string& filename); // Madvise the largest page aligned region within begin and end. int MadviseLargestPageAlignedRegion(const uint8_t* begin, const uint8_t* end, int advice); +// Return whether the location is on system (i.e. android root). +bool LocationIsOnSystem(const char* location); + } // namespace art #endif // ART_RUNTIME_BASE_FILE_UTILS_H_ diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index 92d86519dc..03774f45cd 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -26,7 +26,7 @@ #include "class_linker-inl.h" #include "dex/dex_file-inl.h" #include "dex/dex_instruction-inl.h" -#include "invoke_type.h" +#include "dex/invoke_type.h" #include "mirror/class-inl.h" #include "mirror/method_type.h" #include "mirror/object-inl.h" diff --git a/runtime/dex/dex_file_verifier.cc b/runtime/dex/dex_file_verifier.cc index 5800bb1006..f7fdbb027c 100644 --- a/runtime/dex/dex_file_verifier.cc +++ b/runtime/dex/dex_file_verifier.cc @@ -27,6 +27,7 @@ #include "dex_file-inl.h" #include "experimental_flags.h" #include "leb128.h" +#include "modifiers.h" #include "safe_map.h" #include "utf-inl.h" #include "utils.h" diff --git a/runtime/invoke_type.h b/runtime/dex/invoke_type.h index 2b877e6f51..726d269a3e 100644 --- a/runtime/invoke_type.h +++ b/runtime/dex/invoke_type.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_INVOKE_TYPE_H_ -#define ART_RUNTIME_INVOKE_TYPE_H_ +#ifndef ART_RUNTIME_DEX_INVOKE_TYPE_H_ +#define ART_RUNTIME_DEX_INVOKE_TYPE_H_ #include <iosfwd> @@ -35,4 +35,4 @@ std::ostream& operator<<(std::ostream& os, const InvokeType& rhs); } // namespace art -#endif // ART_RUNTIME_INVOKE_TYPE_H_ +#endif // ART_RUNTIME_DEX_INVOKE_TYPE_H_ diff --git a/runtime/dex/modifiers.cc b/runtime/dex/modifiers.cc new file mode 100644 index 0000000000..30daefb172 --- /dev/null +++ b/runtime/dex/modifiers.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +#include "modifiers.h" + +namespace art { + +std::string PrettyJavaAccessFlags(uint32_t access_flags) { + std::string result; + if ((access_flags & kAccPublic) != 0) { + result += "public "; + } + if ((access_flags & kAccProtected) != 0) { + result += "protected "; + } + if ((access_flags & kAccPrivate) != 0) { + result += "private "; + } + if ((access_flags & kAccFinal) != 0) { + result += "final "; + } + if ((access_flags & kAccStatic) != 0) { + result += "static "; + } + if ((access_flags & kAccAbstract) != 0) { + result += "abstract "; + } + if ((access_flags & kAccInterface) != 0) { + result += "interface "; + } + if ((access_flags & kAccTransient) != 0) { + result += "transient "; + } + if ((access_flags & kAccVolatile) != 0) { + result += "volatile "; + } + if ((access_flags & kAccSynchronized) != 0) { + result += "synchronized "; + } + return result; +} + +} // namespace art diff --git a/runtime/modifiers.h b/runtime/dex/modifiers.h index 0e2db932bb..2998f602d4 100644 --- a/runtime/modifiers.h +++ b/runtime/dex/modifiers.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_MODIFIERS_H_ -#define ART_RUNTIME_MODIFIERS_H_ +#ifndef ART_RUNTIME_DEX_MODIFIERS_H_ +#define ART_RUNTIME_DEX_MODIFIERS_H_ #include <stdint.h> @@ -138,7 +138,11 @@ static constexpr uint32_t kAccValidInterfaceFlags = kAccPublic | kAccInterface | static constexpr uint32_t kAccVisibilityFlags = kAccPublic | kAccPrivate | kAccProtected; +// Returns a human-readable version of the Java part of the access flags, e.g., "private static " +// (note the trailing whitespace). +std::string PrettyJavaAccessFlags(uint32_t access_flags); + } // namespace art -#endif // ART_RUNTIME_MODIFIERS_H_ +#endif // ART_RUNTIME_DEX_MODIFIERS_H_ diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 3048f45f30..9ef7d426df 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -25,12 +25,12 @@ #include "class_linker-inl.h" #include "common_throws.h" #include "dex/dex_file.h" +#include "dex/invoke_type.h" #include "entrypoints/quick/callee_save_frame.h" #include "handle_scope-inl.h" #include "imt_conflict_table.h" #include "imtable-inl.h" #include "indirect_reference_table.h" -#include "invoke_type.h" #include "jni_internal.h" #include "mirror/array.h" #include "mirror/class-inl.h" diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h new file mode 100644 index 0000000000..de3a51a2ac --- /dev/null +++ b/runtime/hidden_api.h @@ -0,0 +1,170 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_HIDDEN_API_H_ +#define ART_RUNTIME_HIDDEN_API_H_ + +#include "hidden_api_access_flags.h" +#include "reflection.h" +#include "runtime.h" + +namespace art { +namespace hiddenapi { + +// Returns true if member with `access flags` should only be accessed from +// boot class path. +inline bool IsMemberHidden(uint32_t access_flags) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + return false; + } + + switch (HiddenApiAccessFlags::DecodeFromRuntime(access_flags)) { + case HiddenApiAccessFlags::kWhitelist: + case HiddenApiAccessFlags::kLightGreylist: + case HiddenApiAccessFlags::kDarkGreylist: + return false; + case HiddenApiAccessFlags::kBlacklist: + return true; + } +} + +// Returns true if we should warn about non-boot class path accessing member +// with `access_flags`. +inline bool ShouldWarnAboutMember(uint32_t access_flags) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + return false; + } + + switch (HiddenApiAccessFlags::DecodeFromRuntime(access_flags)) { + case HiddenApiAccessFlags::kWhitelist: + return false; + case HiddenApiAccessFlags::kLightGreylist: + case HiddenApiAccessFlags::kDarkGreylist: + return true; + case HiddenApiAccessFlags::kBlacklist: + // We should never access a blacklisted member from non-boot class path, + // but this function is called before we establish the origin of the access. + // Return false here, we do not want to warn when boot class path accesses + // a blacklisted member. + return false; + } +} + +// Returns true if caller `num_frames` up the stack is in boot class path. +inline bool IsCallerInBootClassPath(Thread* self, size_t num_frames) + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Class> klass = GetCallingClass(self, num_frames); + if (klass == nullptr) { + // Unattached native thread. Assume that this is *not* boot class path. + return false; + } + return klass->IsBootStrapClassLoaded(); +} + +// Returns true if `caller` should not be allowed to access member with `access_flags`. +inline bool ShouldBlockAccessToMember(uint32_t access_flags, mirror::Class* caller) + REQUIRES_SHARED(Locks::mutator_lock_) { + return IsMemberHidden(access_flags) && + !caller->IsBootStrapClassLoaded(); +} + +// Returns true if `caller` should not be allowed to access `member`. +template<typename T> +inline bool ShouldBlockAccessToMember(T* member, ArtMethod* caller) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + DCHECK(!caller->IsRuntimeMethod()); + return ShouldBlockAccessToMember(member->GetAccessFlags(), caller->GetDeclaringClass()); +} + +// Returns true if the caller `num_frames` up the stack should not be allowed +// to access `member`. +template<typename T> +inline bool ShouldBlockAccessToMember(T* member, Thread* self, size_t num_frames) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + return IsMemberHidden(member->GetAccessFlags()) && + !IsCallerInBootClassPath(self, num_frames); // This is expensive. Save it for last. +} + +// Issue a warning about field access. +inline void WarnAboutMemberAccess(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_) { + Runtime::Current()->SetPendingHiddenApiWarning(true); + LOG(WARNING) << "Access to hidden field " << field->PrettyField(); +} + +// Issue a warning about method access. +inline void WarnAboutMemberAccess(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { + Runtime::Current()->SetPendingHiddenApiWarning(true); + LOG(WARNING) << "Access to hidden method " << method->PrettyMethod(); +} + +// Set access flags of `member` to be in hidden API whitelist. This can be disabled +// with a Runtime::SetDedupHiddenApiWarnings. +template<typename T> +inline void MaybeWhitelistMember(T* member) REQUIRES_SHARED(Locks::mutator_lock_) { + if (Runtime::Current()->ShouldDedupeHiddenApiWarnings()) { + member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( + member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); + DCHECK(!ShouldWarnAboutMember(member->GetAccessFlags())); + } +} + +// Check if `caller` should be allowed to access `member` and warn if not. +template<typename T> +inline void MaybeWarnAboutMemberAccess(T* member, ArtMethod* caller) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + DCHECK(!caller->IsRuntimeMethod()); + if (!Runtime::Current()->AreHiddenApiChecksEnabled() || + member == nullptr || + !ShouldWarnAboutMember(member->GetAccessFlags()) || + caller->GetDeclaringClass()->IsBootStrapClassLoaded()) { + return; + } + + WarnAboutMember(member); + MaybeWhitelistMember(member); +} + +// Check if the caller `num_frames` up the stack should be allowed to access +// `member` and warn if not. +template<typename T> +inline void MaybeWarnAboutMemberAccess(T* member, Thread* self, size_t num_frames) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled() || + member == nullptr || + !ShouldWarnAboutMember(member->GetAccessFlags())) { + return; + } + + // Walk the stack to find the caller. This is *very* expensive. Save it for last. + ObjPtr<mirror::Class> klass = GetCallingClass(self, num_frames); + if (klass == nullptr) { + // Unattached native thread, assume that this is *not* boot class path + // and enforce the rules. + } else if (klass->IsBootStrapClassLoaded()) { + return; + } + + WarnAboutMemberAccess(member); + MaybeWhitelistMember(member); +} + +} // namespace hiddenapi +} // namespace art + +#endif // ART_RUNTIME_HIDDEN_API_H_ diff --git a/runtime/hidden_api_access_flags.h b/runtime/hidden_api_access_flags.h index 80a002d96e..c328f965d2 100644 --- a/runtime/hidden_api_access_flags.h +++ b/runtime/hidden_api_access_flags.h @@ -18,7 +18,7 @@ #define ART_RUNTIME_HIDDEN_API_ACCESS_FLAGS_H_ #include "base/bit_utils.h" -#include "modifiers.h" +#include "dex/modifiers.h" namespace art { diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index d1436fa9cf..b9b00519d1 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -38,6 +38,7 @@ #include "entrypoints/entrypoint_utils-inl.h" #include "gc/reference_processor.h" #include "handle_scope-inl.h" +#include "hidden_api.h" #include "interpreter/interpreter_common.h" #include "jvalue-inl.h" #include "mirror/array-inl.h" @@ -265,7 +266,11 @@ void UnstartedRuntime::UnstartedClassNewInstance( bool ok = false; auto* cl = Runtime::Current()->GetClassLinker(); if (cl->EnsureInitialized(self, h_klass, true, true)) { - auto* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize()); + ArtMethod* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize()); + if (cons != nullptr && + hiddenapi::ShouldBlockAccessToMember(cons, shadow_frame->GetMethod())) { + cons = nullptr; + } if (cons != nullptr) { Handle<mirror::Object> h_obj(hs.NewHandle(klass->AllocObject(self))); CHECK(h_obj != nullptr); // We don't expect OOM at compile-time. @@ -308,6 +313,10 @@ void UnstartedRuntime::UnstartedClassGetDeclaredField( } } } + if (found != nullptr && + hiddenapi::ShouldBlockAccessToMember(found, shadow_frame->GetMethod())) { + found = nullptr; + } if (found == nullptr) { AbortTransactionOrFail(self, "Failed to find field in Class.getDeclaredField in un-started " " runtime. name=%s class=%s", name2->ToModifiedUtf8().c_str(), @@ -370,6 +379,10 @@ void UnstartedRuntime::UnstartedClassGetDeclaredMethod( self, klass, name, args); } } + if (method != nullptr && + hiddenapi::ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame->GetMethod())) { + method = nullptr; + } result->SetL(method); } @@ -404,6 +417,11 @@ void UnstartedRuntime::UnstartedClassGetDeclaredConstructor( false>(self, klass, args); } } + if (constructor != nullptr && + hiddenapi::ShouldBlockAccessToMember( + constructor->GetArtMethod(), shadow_frame->GetMethod())) { + constructor = nullptr; + } result->SetL(constructor); } diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 33fa0d6a26..4bf2895723 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -2030,4 +2030,28 @@ bool ProfileCompilationInfo::IsProfileFile(int fd) { return memcmp(buffer, kProfileMagic, byte_count) == 0; } +bool ProfileCompilationInfo::UpdateProfileKeys( + const std::vector<std::unique_ptr<const DexFile>>& dex_files) { + for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { + for (DexFileData* dex_data : info_) { + if (dex_data->checksum == dex_file->GetLocationChecksum() + && dex_data->num_method_ids == dex_file->NumMethodIds()) { + std::string new_profile_key = GetProfileDexFileKey(dex_file->GetLocation()); + if (dex_data->profile_key != new_profile_key) { + if (profile_key_map_.find(new_profile_key) != profile_key_map_.end()) { + // We can't update the key if the new key belongs to a different dex file. + LOG(ERROR) << "Cannot update profile key to " << new_profile_key + << " because the new key belongs to another dex file."; + return false; + } + profile_key_map_.erase(dex_data->profile_key); + profile_key_map_.Put(new_profile_key, dex_data->profile_index); + dex_data->profile_key = new_profile_key; + } + } + } + } + return true; +} + } // namespace art diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index 29a4c115fa..350ce9ed8d 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -416,6 +416,17 @@ class ProfileCompilationInfo { // Return true if the fd points to a profile file. bool IsProfileFile(int fd); + // Update the profile keys corresponding to the given dex files based on their current paths. + // This method allows fix-ups in the profile for dex files that might have been renamed. + // The new profile key will be constructed based on the current dex location. + // + // The matching [profile key <-> dex_file] is done based on the dex checksum and the number of + // methods ids. If neither is a match then the profile key is not updated. + // + // If the new profile key would collide with an existing key (for a different dex) + // the method returns false. Otherwise it returns true. + bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files); + private: enum ProfileLoadStatus { kProfileLoadWouldOverwiteData, diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index 55989d8c52..b4265d1a28 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -22,6 +22,7 @@ #include "class_linker-inl.h" #include "common_runtime_test.h" #include "dex/dex_file.h" +#include "dex/dex_file_loader.h" #include "handle_scope-inl.h" #include "jit/profile_compilation_info.h" #include "linear_alloc.h" @@ -1038,4 +1039,89 @@ TEST_F(ProfileCompilationInfoTest, LoadFromZipFailBadProfile) { ASSERT_FALSE(loaded_info.Load(GetFd(zip))); } +TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOk) { + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex"); + + ProfileCompilationInfo info; + for (const std::unique_ptr<const DexFile>& dex : dex_files) { + // Create the profile with a different location so that we can update it to the + // real dex location later. + std::string base_location = DexFileLoader::GetBaseLocation(dex->GetLocation()); + std::string multidex_suffix = DexFileLoader::GetMultiDexSuffix(dex->GetLocation()); + std::string old_name = base_location + "-old" + multidex_suffix; + info.AddMethodIndex(Hotness::kFlagHot, + old_name, + dex->GetLocationChecksum(), + /* method_idx */ 0, + dex->NumMethodIds()); + } + + // Update the profile keys based on the original dex files + ASSERT_TRUE(info.UpdateProfileKeys(dex_files)); + + // Verify that we find the methods when searched with the original dex files. + for (const std::unique_ptr<const DexFile>& dex : dex_files) { + std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> loaded_pmi = + info.GetMethod(dex->GetLocation(), dex->GetLocationChecksum(), /* method_idx */ 0); + ASSERT_TRUE(loaded_pmi != nullptr); + } +} + +TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyOkButNoUpdate) { + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex"); + + ProfileCompilationInfo info; + info.AddMethodIndex(Hotness::kFlagHot, + "my.app", + /* checksum */ 123, + /* method_idx */ 0, + /* num_method_ids */ 10); + + // Update the profile keys based on the original dex files + ASSERT_TRUE(info.UpdateProfileKeys(dex_files)); + + // Verify that we did not perform any update and that we cannot find anything with the new + // location. + for (const std::unique_ptr<const DexFile>& dex : dex_files) { + std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> loaded_pmi = + info.GetMethod(dex->GetLocation(), dex->GetLocationChecksum(), /* method_idx */ 0); + ASSERT_TRUE(loaded_pmi == nullptr); + } + + // Verify that we can find the original entry. + std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> loaded_pmi = + info.GetMethod("my.app", /* checksum */ 123, /* method_idx */ 0); + ASSERT_TRUE(loaded_pmi != nullptr); +} + +TEST_F(ProfileCompilationInfoTest, UpdateProfileKeyFail) { + std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("MultiDex"); + + + ProfileCompilationInfo info; + // Add all dex + for (const std::unique_ptr<const DexFile>& dex : dex_files) { + // Create the profile with a different location so that we can update it to the + // real dex location later. + std::string base_location = DexFileLoader::GetBaseLocation(dex->GetLocation()); + std::string multidex_suffix = DexFileLoader::GetMultiDexSuffix(dex->GetLocation()); + std::string old_name = base_location + "-old" + multidex_suffix; + info.AddMethodIndex(Hotness::kFlagHot, + old_name, + dex->GetLocationChecksum(), + /* method_idx */ 0, + dex->NumMethodIds()); + } + + // Add a method index using the location we want to rename to. + // This will cause the rename to fail because an existing entry would already have that name. + info.AddMethodIndex(Hotness::kFlagHot, + dex_files[0]->GetLocation(), + /* checksum */ 123, + /* method_idx */ 0, + dex_files[0]->NumMethodIds()); + + ASSERT_FALSE(info.UpdateProfileKeys(dex_files)); +} + } // namespace art diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index b8e6ebe8d8..c40360f612 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -34,6 +34,7 @@ #include "class_linker-inl.h" #include "dex/dex_file-inl.h" #include "fault_handler.h" +#include "hidden_api.h" #include "gc/accounting/card_table-inl.h" #include "gc_root.h" #include "indirect_reference_table-inl.h" @@ -79,15 +80,17 @@ namespace art { // things not rendering correctly. E.g. b/16858794 static constexpr bool kWarnJniAbort = false; -// Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set -// up handles for the rare case where these actually do something. Once these functions return it is -// possible there will be a pending exception if the instrumentation happens to throw one. +// Helpers to check if we need to warn about accessing hidden API fields and to call instrumentation +// functions for them. These take jobjects so we don't need to set up handles for the rare case +// where these actually do something. Once these functions return it is possible there will be +// a pending exception if the instrumentation happens to throw one. static void NotifySetObjectField(ArtField* field, jobject obj, jobject jval) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK_EQ(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot); + Thread* self = Thread::Current(); + hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { - Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -112,9 +115,10 @@ static void NotifySetObjectField(ArtField* field, jobject obj, jobject jval) static void NotifySetPrimitiveField(ArtField* field, jobject obj, JValue val) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK_NE(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot); + Thread* self = Thread::Current(); + hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { - Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -136,9 +140,10 @@ static void NotifySetPrimitiveField(ArtField* field, jobject obj, JValue val) static void NotifyGetField(ArtField* field, jobject obj) REQUIRES_SHARED(Locks::mutator_lock_) { + Thread* self = Thread::Current(); + hiddenapi::MaybeWarnAboutMemberAccess(field, self, /* num_frames */ 1); instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation(); if (UNLIKELY(instrumentation->HasFieldReadListeners())) { - Thread* self = Thread::Current(); ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr, /*check_suspended*/ true, /*abort_on_error*/ false); @@ -238,6 +243,10 @@ static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, } else { method = c->FindClassMethod(name, sig, pointer_size); } + if (method != nullptr && + hiddenapi::ShouldBlockAccessToMember(method, soa.Self(), /* num_frames */ 1)) { + method = nullptr; + } if (method == nullptr || method->IsStatic() != is_static) { ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static"); return nullptr; @@ -314,6 +323,10 @@ static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, con } else { field = c->FindInstanceField(name, field_type->GetDescriptor(&temp)); } + if (field != nullptr && + hiddenapi::ShouldBlockAccessToMember(field, soa.Self(), /* num_frames */ 1)) { + field = nullptr; + } if (field == nullptr) { soa.Self()->ThrowNewExceptionF("Ljava/lang/NoSuchFieldError;", "no \"%s\" field \"%s\" in class \"%s\" or its superclasses", diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 302a5e622e..36388eb3aa 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -26,11 +26,12 @@ #include "class_linker.h" #include "class_loader.h" #include "common_throws.h" -#include "dex_cache.h" #include "dex/dex_file-inl.h" +#include "dex/invoke_type.h" +#include "dex_cache.h" #include "gc/heap-inl.h" +#include "hidden_api.h" #include "iftable.h" -#include "invoke_type.h" #include "subtype_check.h" #include "object-inl.h" #include "object_array.h" @@ -1143,6 +1144,10 @@ inline bool Class::CanAccessMember(ObjPtr<Class> access_to, uint32_t member_flag if (this == access_to) { return true; } + // Do not allow non-boot class path classes access hidden APIs. + if (hiddenapi::ShouldBlockAccessToMember(member_flags, this)) { + return false; + } // Public members are trivially accessible if (member_flags & kAccPublic) { return true; diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 84b032620f..ced7c7c908 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -25,10 +25,10 @@ #include "class_status.h" #include "dex/dex_file.h" #include "dex/dex_file_types.h" +#include "dex/modifiers.h" #include "gc/allocator_type.h" #include "gc_root.h" #include "imtable.h" -#include "modifiers.h" #include "object.h" #include "object_array.h" #include "primitive.h" diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h index 6845575d18..dd09be331a 100644 --- a/runtime/mirror/field.h +++ b/runtime/mirror/field.h @@ -19,8 +19,8 @@ #include "accessible_object.h" #include "base/enums.h" +#include "dex/modifiers.h" #include "gc_root.h" -#include "modifiers.h" #include "obj_ptr.h" #include "object.h" #include "primitive.h" diff --git a/runtime/mirror/method_handles_lookup.cc b/runtime/mirror/method_handles_lookup.cc index a390a2ef53..039bbf2932 100644 --- a/runtime/mirror/method_handles_lookup.cc +++ b/runtime/mirror/method_handles_lookup.cc @@ -17,11 +17,11 @@ #include "method_handles_lookup.h" #include "class-inl.h" +#include "dex/modifiers.h" #include "gc_root-inl.h" #include "handle_scope.h" #include "jni_internal.h" #include "mirror/method_handle_impl.h" -#include "modifiers.h" #include "object-inl.h" #include "well_known_classes.h" diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index e22726b79b..2892967a51 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -286,7 +286,7 @@ static void ZygoteHooks_nativePostForkChild(JNIEnv* env, } if ((runtime_flags & DISABLE_HIDDEN_API_CHECKS) != 0) { - Runtime::Current()->DisableHiddenApiChecks(); + Runtime::Current()->SetHiddenApiChecksEnabled(false); runtime_flags &= ~DISABLE_HIDDEN_API_CHECKS; } diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 7b999c04af..5544275984 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -25,6 +25,7 @@ #include "common_throws.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" +#include "hidden_api.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" @@ -47,6 +48,75 @@ namespace art { +ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + return false; + } + + // Walk the stack and find the first frame not from java.lang.Class. + // This is very expensive. Save this till the last. + struct FirstNonClassClassCallerVisitor : public StackVisitor { + explicit FirstNonClassClassCallerVisitor(Thread* thread) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + caller(nullptr) { + } + + bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) { + ArtMethod *m = GetMethod(); + if (m == nullptr) { + // Attached native thread. Assume this is *not* boot class path. + caller = nullptr; + return false; + } else if (m->IsRuntimeMethod()) { + // Internal runtime method, continue walking the stack. + return true; + } else if (m->GetDeclaringClass()->IsClassClass()) { + return true; + } else { + caller = m; + return false; + } + } + + ArtMethod* caller; + }; + + FirstNonClassClassCallerVisitor visitor(self); + visitor.WalkStack(); + return visitor.caller == nullptr || + !visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded(); +} + +// Returns true if the first non-ClassClass caller up the stack should not be +// allowed access to `member`. +template<typename T> +ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + return hiddenapi::IsMemberHidden(member->GetAccessFlags()) && + ShouldEnforceHiddenApi(self); +} + +// Returns true if a class member should be discoverable with reflection given +// the criteria. Some reflection calls only return public members +// (public_only == true), some members should be hidden from non-boot class path +// callers (enforce_hidden_api == true). +ALWAYS_INLINE static bool IsDiscoverable(bool public_only, + bool enforce_hidden_api, + uint32_t access_flags) { + if (public_only && ((access_flags & kAccPublic) == 0)) { + return false; + } + + if (enforce_hidden_api && hiddenapi::IsMemberHidden(access_flags)) { + return false; + } + + return true; +} + + ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass( const ScopedFastNativeObjectAccess& soa, jobject java_class) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -164,17 +234,16 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields(); IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields(); size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields(); - if (public_only) { - // Lets go subtract all the non public fields. - for (ArtField& field : ifields) { - if (!field.IsPublic()) { - --array_size; - } + bool enforce_hidden_api = ShouldEnforceHiddenApi(self); + // Lets go subtract all the non discoverable fields. + for (ArtField& field : ifields) { + if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { + --array_size; } - for (ArtField& field : sfields) { - if (!field.IsPublic()) { - --array_size; - } + } + for (ArtField& field : sfields) { + if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { + --array_size; } } size_t array_idx = 0; @@ -184,7 +253,7 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( return nullptr; } for (ArtField& field : ifields) { - if (!public_only || field.IsPublic()) { + if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self, &field, force_resolve); @@ -199,7 +268,7 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( } } for (ArtField& field : sfields) { - if (!public_only || field.IsPublic()) { + if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self, &field, force_resolve); @@ -354,8 +423,13 @@ static jobject Class_getPublicFieldRecursive(JNIEnv* env, jobject javaThis, jstr ThrowNullPointerException("name == null"); return nullptr; } - return soa.AddLocalReference<jobject>( - GetPublicFieldRecursive(soa.Self(), DecodeClass(soa, javaThis), name_string)); + + mirror::Field* field = GetPublicFieldRecursive( + soa.Self(), DecodeClass(soa, javaThis), name_string); + if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) { + return nullptr; + } + return soa.AddLocalReference<jobject>(field); } static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) { @@ -369,7 +443,7 @@ static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring nam Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis)); Handle<mirror::Field> result = hs.NewHandle(GetDeclaredField(soa.Self(), h_klass.Get(), h_string.Get())); - if (result == nullptr) { + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtField(), soa.Self())) { std::string name_str = h_string->ToModifiedUtf8(); if (name_str == "value" && h_klass->IsStringClass()) { // We log the error for this specific case, as the user might just swallow the exception. @@ -399,24 +473,32 @@ static jobject Class_getDeclaredConstructorInternal( soa.Self(), DecodeClass(soa, javaThis), soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { + return nullptr; + } return soa.AddLocalReference<jobject>(result); } -static ALWAYS_INLINE inline bool MethodMatchesConstructor(ArtMethod* m, bool public_only) +static ALWAYS_INLINE inline bool MethodMatchesConstructor( + ArtMethod* m, bool public_only, bool enforce_hidden_api) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(m != nullptr); - return (!public_only || m->IsPublic()) && !m->IsStatic() && m->IsConstructor(); + return m->IsConstructor() && + !m->IsStatic() && + IsDiscoverable(public_only, enforce_hidden_api, m->GetAccessFlags()); } static jobjectArray Class_getDeclaredConstructorsInternal( JNIEnv* env, jobject javaThis, jboolean publicOnly) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); + bool public_only = (publicOnly != JNI_FALSE); + bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self()); Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis)); size_t constructor_count = 0; // Two pass approach for speed. for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) { - constructor_count += MethodMatchesConstructor(&m, publicOnly != JNI_FALSE) ? 1u : 0u; + constructor_count += MethodMatchesConstructor(&m, public_only, enforce_hidden_api) ? 1u : 0u; } auto h_constructors = hs.NewHandle(mirror::ObjectArray<mirror::Constructor>::Alloc( soa.Self(), mirror::Constructor::ArrayClass(), constructor_count)); @@ -426,7 +508,7 @@ static jobjectArray Class_getDeclaredConstructorsInternal( } constructor_count = 0; for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) { - if (MethodMatchesConstructor(&m, publicOnly != JNI_FALSE)) { + if (MethodMatchesConstructor(&m, public_only, enforce_hidden_api)) { DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); auto* constructor = mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>( @@ -452,6 +534,9 @@ static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, DecodeClass(soa, javaThis), soa.Decode<mirror::String>(name), soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { + return nullptr; + } return soa.AddLocalReference<jobject>(result); } @@ -459,13 +544,17 @@ static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaT jboolean publicOnly) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); + + bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self()); + bool public_only = (publicOnly != JNI_FALSE); + Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis)); size_t num_methods = 0; - for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { - auto modifiers = m.GetAccessFlags(); + for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { + uint32_t modifiers = m.GetAccessFlags(); // Add non-constructor declared methods. - if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) && - (modifiers & kAccConstructor) == 0) { + if ((modifiers & kAccConstructor) == 0 && + IsDiscoverable(public_only, enforce_hidden_api, modifiers)) { ++num_methods; } } @@ -476,10 +565,10 @@ static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaT return nullptr; } num_methods = 0; - for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { - auto modifiers = m.GetAccessFlags(); - if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) && - (modifiers & kAccConstructor) == 0) { + for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { + uint32_t modifiers = m.GetAccessFlags(); + if ((modifiers & kAccConstructor) == 0 && + IsDiscoverable(public_only, enforce_hidden_api, modifiers)) { DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); auto* method = @@ -693,11 +782,11 @@ static jobject Class_newInstance(JNIEnv* env, jobject javaThis) { return nullptr; } } - auto* constructor = klass->GetDeclaredConstructor( + ArtMethod* constructor = klass->GetDeclaredConstructor( soa.Self(), ScopedNullHandle<mirror::ObjectArray<mirror::Class>>(), kRuntimePointerSize); - if (UNLIKELY(constructor == nullptr)) { + if (UNLIKELY(constructor == nullptr) || ShouldBlockAccessToMember(constructor, soa.Self())) { soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;", "%s has no zero argument constructor", klass->PrettyClass().c_str()); @@ -742,6 +831,7 @@ static jobject Class_newInstance(JNIEnv* env, jobject javaThis) { return nullptr; } } + hiddenapi::MaybeWarnAboutMemberAccess(constructor, soa.Self(), /* num_frames */ 1); // Invoke the constructor. JValue result; uint32_t args[1] = { static_cast<uint32_t>(reinterpret_cast<uintptr_t>(receiver.Get())) }; diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc index f990c0421d..db7f4bb18c 100644 --- a/runtime/native/java_lang_reflect_Field.cc +++ b/runtime/native/java_lang_reflect_Field.cc @@ -25,6 +25,7 @@ #include "common_throws.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" +#include "hidden_api.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/field-inl.h" @@ -161,6 +162,9 @@ static jobject Field_get(JNIEnv* env, jobject javaField, jobject javaObj) { DCHECK(soa.Self()->IsExceptionPending()); return nullptr; } + + hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); + // We now don't expect suspension unless an exception is thrown. // Get the field's value, boxing if necessary. Primitive::Type field_type = f->GetTypeAsPrimitiveType(); @@ -183,13 +187,14 @@ ALWAYS_INLINE inline static JValue GetPrimitiveField(JNIEnv* env, DCHECK(soa.Self()->IsExceptionPending()); return JValue(); } - // If field is not set to be accessible, verify it can be accessed by the caller. if (!f->IsAccessible() && !VerifyFieldAccess<false>(soa.Self(), f, o)) { DCHECK(soa.Self()->IsExceptionPending()); return JValue(); } + hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); + // We now don't expect suspension unless an exception is thrown. // Read the value. Primitive::Type field_type = f->GetTypeAsPrimitiveType(); @@ -351,11 +356,15 @@ static void Field_set(JNIEnv* env, jobject javaField, jobject javaObj, jobject j DCHECK(soa.Self()->IsExceptionPending()); return; } + // If field is not set to be accessible, verify it can be accessed by the caller. if (!f->IsAccessible() && !VerifyFieldAccess<true>(soa.Self(), f, o)) { DCHECK(soa.Self()->IsExceptionPending()); return; } + + hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); + SetFieldValue(o, f, field_prim_type, true, unboxed_value); } @@ -391,6 +400,8 @@ static void SetPrimitiveField(JNIEnv* env, return; } + hiddenapi::MaybeWarnAboutMemberAccess(f->GetArtField(), soa.Self(), /* num_frames */ 1); + // Write the value. SetFieldValue(o, f, field_type, false, wide_value); } diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 73ca19a363..15a5954396 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -72,9 +72,12 @@ std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStat OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable) + bool load_executable, + bool only_load_system_executable) : OatFileAssistant(dex_location, - isa, load_executable, + isa, + load_executable, + only_load_system_executable, -1 /* vdex_fd */, -1 /* oat_fd */, -1 /* zip_fd */) {} @@ -83,11 +86,13 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, bool load_executable, + bool only_load_system_executable, int vdex_fd, int oat_fd, int zip_fd) : isa_(isa), load_executable_(load_executable), + only_load_system_executable_(only_load_system_executable), odex_(this, /*is_oat_location*/ false), oat_(this, /*is_oat_location*/ true), zip_fd_(zip_fd) { @@ -1122,6 +1127,10 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { if (!load_attempted_) { load_attempted_ = true; if (filename_provided_) { + bool executable = oat_file_assistant_->load_executable_; + if (executable && oat_file_assistant_->only_load_system_executable_) { + executable = LocationIsOnSystem(filename_.c_str()); + } std::string error_msg; if (use_fd_) { if (oat_fd_ >= 0 && vdex_fd_ >= 0) { @@ -1130,7 +1139,7 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { filename_.c_str(), nullptr, nullptr, - oat_file_assistant_->load_executable_, + executable, false /* low_4gb */, oat_file_assistant_->dex_location_.c_str(), &error_msg)); @@ -1140,7 +1149,7 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { filename_.c_str(), nullptr, nullptr, - oat_file_assistant_->load_executable_, + executable, false /* low_4gb */, oat_file_assistant_->dex_location_.c_str(), &error_msg)); diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 6c01c1e880..a6140304c2 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -119,9 +119,13 @@ class OatFileAssistant { // // load_executable should be true if the caller intends to try and load // executable code for this dex location. + // + // only_load_system_executable should be true if the caller intends to have + // only oat files from /system loaded executable. OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable); + bool load_executable, + bool only_load_system_executable = false); // Similar to this(const char*, const InstructionSet, bool), however, if a valid zip_fd is // provided, vdex, oat, and zip files will be read from vdex_fd, oat_fd and zip_fd respectively. @@ -129,6 +133,7 @@ class OatFileAssistant { OatFileAssistant(const char* dex_location, const InstructionSet isa, bool load_executable, + bool only_load_system_executable, int vdex_fd, int oat_fd, int zip_fd); @@ -487,6 +492,9 @@ class OatFileAssistant { // Whether we will attempt to load oat files executable. bool load_executable_ = false; + // Whether only oat files on /system are loaded executable. + const bool only_load_system_executable_ = false; + // Cached value of the required dex checksums. // This should be accessed only by the GetRequiredDexChecksums() method. std::vector<uint32_t> cached_required_dex_checksums_; diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index a98da0f029..50f5e7a0d5 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -246,6 +246,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, vdex_fd.get(), odex_fd.get(), zip_fd.get()); @@ -285,6 +286,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, vdex_fd.get(), -1 /* oat_fd */, zip_fd.get()); @@ -319,6 +321,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, -1 /* vdex_fd */, odex_fd.get(), zip_fd.get()); @@ -342,6 +345,7 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false, + false, -1 /* vdex_fd */, -1 /* oat_fd */, zip_fd); @@ -1439,6 +1443,60 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { default_filter, false, false, relative_context.get())); } +TEST_F(OatFileAssistantTest, SystemOdex) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; + std::string system_location = GetAndroidRoot() + "/OatUpToDate.jar"; + + std::string error_msg; + + Copy(GetDexSrc1(), dex_location); + EXPECT_FALSE(LocationIsOnSystem(dex_location.c_str())); + + { + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + true, + false); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + { + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + true, + true); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_FALSE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + Copy(GetDexSrc1(), system_location); + EXPECT_TRUE(LocationIsOnSystem(system_location.c_str())); + + { + OatFileAssistant oat_file_assistant(system_location.c_str(), + kRuntimeISA, + true, + false); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } + + { + OatFileAssistant oat_file_assistant(system_location.c_str(), + kRuntimeISA, + true, + true); + int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); + EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; + EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); + } +} + // TODO: More Tests: // * Test class linker falls back to unquickened dex for DexNoOat // * Test class linker falls back to unquickened dex for MultiDexNoOat diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 9503360167..e4194442d3 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -56,15 +56,11 @@ using android::base::StringPrintf; // If true, we attempt to load the application image if it exists. static constexpr bool kEnableAppImage = true; -static bool OatFileIsOnSystem(const std::unique_ptr<const OatFile>& oat_file) { - UniqueCPtr<const char[]> path(realpath(oat_file->GetLocation().c_str(), nullptr)); - return path != nullptr && android::base::StartsWith(oat_file->GetLocation(), - GetAndroidRoot().c_str()); -} - const OatFile* OatFileManager::RegisterOatFile(std::unique_ptr<const OatFile> oat_file) { WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_); - CHECK(!only_use_system_oat_files_ || OatFileIsOnSystem(oat_file)) + CHECK(!only_use_system_oat_files_ || + LocationIsOnSystem(oat_file->GetLocation().c_str()) || + !oat_file->IsExecutable()) << "Registering a non /system oat file: " << oat_file->GetLocation(); DCHECK(oat_file != nullptr); if (kIsDebugBuild) { @@ -424,7 +420,8 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( OatFileAssistant oat_file_assistant(dex_location, kRuntimeISA, - !runtime->IsAotCompiler()); + !runtime->IsAotCompiler(), + only_use_system_oat_files_); // Lock the target oat location to avoid races generating and loading the // oat file. @@ -437,8 +434,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const OatFile* source_oat_file = nullptr; - // No point in trying to make up-to-date if we can only use system oat files. - if (!only_use_system_oat_files_ && !oat_file_assistant.IsUpToDate()) { + if (!oat_file_assistant.IsUpToDate()) { // Update the oat file on disk if we can, based on the --compiler-filter // option derived from the current runtime options. // This may fail, but that's okay. Best effort is all that matters here. @@ -474,9 +470,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); - if (oat_file != nullptr && only_use_system_oat_files_ && !OatFileIsOnSystem(oat_file)) { - // If the oat file is not on /system, don't use it. - } else if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { + if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it // could load oat files without checking the classpath, which would be incorrect. diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h index dd6b7ba2ff..038474e31f 100644 --- a/runtime/oat_file_manager.h +++ b/runtime/oat_file_manager.h @@ -127,6 +127,9 @@ class OatFileManager { std::set<std::unique_ptr<const OatFile>> oat_files_ GUARDED_BY(Locks::oat_file_manager_lock_); bool have_non_pic_oat_file_; + + // Only use the compiled code in an OAT file when the file is on /system. If the OAT file + // is not on /system, don't load it "executable". bool only_use_system_oat_files_; DISALLOW_COPY_AND_ASSIGN(OatFileManager); diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 635a03afe0..6ffafe02f1 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -465,6 +465,9 @@ JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject o } ArtMethod* method = jni::DecodeArtMethod(mid); + + hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); + bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -496,6 +499,9 @@ JValue InvokeWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, jobject o } ArtMethod* method = jni::DecodeArtMethod(mid); + + hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); + bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -528,6 +534,9 @@ JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnab ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj); ArtMethod* method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid)); + + hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); + bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -560,6 +569,9 @@ JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnab ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj); ArtMethod* method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid)); + + hiddenapi::MaybeWarnAboutMemberAccess(method, soa.Self(), /* num_frames */ 1); + bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor(); if (is_string_init) { // Replace calls to String.<init> with equivalent StringFactory call. @@ -604,6 +616,8 @@ jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaM } } + hiddenapi::MaybeWarnAboutMemberAccess(m, soa.Self(), num_frames); + ObjPtr<mirror::Object> receiver; if (!m->IsStatic()) { // Replace calls to String.<init> with equivalent StringFactory call. diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 33bebe0887..6d065d6146 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -266,6 +266,8 @@ Runtime::Runtime() is_low_memory_mode_(false), safe_mode_(false), do_hidden_api_checks_(false), + pending_hidden_api_warning_(false), + dedupe_hidden_api_warnings_(true), dump_native_stack_on_sig_quit_(true), pruned_dalvik_cache_(false), // Initially assume we perceive jank in case the process state is never updated. diff --git a/runtime/runtime.h b/runtime/runtime.h index 022a1be124..184e4e5b91 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -520,14 +520,30 @@ class Runtime { bool IsVerificationEnabled() const; bool IsVerificationSoftFail() const; - void DisableHiddenApiChecks() { - do_hidden_api_checks_ = false; + void SetHiddenApiChecksEnabled(bool value) { + do_hidden_api_checks_ = value; } bool AreHiddenApiChecksEnabled() const { return do_hidden_api_checks_; } + void SetPendingHiddenApiWarning(bool value) { + pending_hidden_api_warning_ = value; + } + + bool HasPendingHiddenApiWarning() const { + return pending_hidden_api_warning_; + } + + void SetDedupeHiddenApiWarnings(bool value) { + dedupe_hidden_api_warnings_ = value; + } + + bool ShouldDedupeHiddenApiWarnings() { + return dedupe_hidden_api_warnings_; + } + bool IsDexFileFallbackEnabled() const { return allow_dex_file_fallback_; } @@ -968,6 +984,14 @@ class Runtime { // Whether access checks on hidden API should be performed. bool do_hidden_api_checks_; + // Whether the application has used an API which is not restricted but we + // should issue a warning about it. + bool pending_hidden_api_warning_; + + // Do not warn about the same hidden API access violation twice. + // This is only used for testing. + bool dedupe_hidden_api_warnings_; + // Whether threads should dump their native stack on SIGQUIT. bool dump_native_stack_on_sig_quit_; diff --git a/runtime/runtime_intrinsics.cc b/runtime/runtime_intrinsics.cc index f710ebeb4c..3295a86e59 100644 --- a/runtime/runtime_intrinsics.cc +++ b/runtime/runtime_intrinsics.cc @@ -18,8 +18,8 @@ #include "art_method-inl.h" #include "class_linker.h" +#include "dex/invoke_type.h" #include "intrinsics_enum.h" -#include "invoke_type.h" #include "mirror/class.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" diff --git a/runtime/utf.cc b/runtime/utf.cc index 93fcb32136..32ae187297 100644 --- a/runtime/utf.cc +++ b/runtime/utf.cc @@ -18,8 +18,7 @@ #include <android-base/logging.h> -#include "mirror/array.h" -#include "mirror/object-inl.h" +#include "base/casts.h" #include "utf-inl.h" namespace art { diff --git a/runtime/utils.cc b/runtime/utils.cc index 79ddcb9bff..b2ec669f32 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -30,7 +30,6 @@ #include "android-base/stringprintf.h" #include "android-base/strings.h" -#include "dex/dex_file-inl.h" #include "os.h" #include "utf-inl.h" @@ -126,41 +125,6 @@ std::string PrettyDescriptor(const char* descriptor) { return result; } -std::string PrettyJavaAccessFlags(uint32_t access_flags) { - std::string result; - if ((access_flags & kAccPublic) != 0) { - result += "public "; - } - if ((access_flags & kAccProtected) != 0) { - result += "protected "; - } - if ((access_flags & kAccPrivate) != 0) { - result += "private "; - } - if ((access_flags & kAccFinal) != 0) { - result += "final "; - } - if ((access_flags & kAccStatic) != 0) { - result += "static "; - } - if ((access_flags & kAccAbstract) != 0) { - result += "abstract "; - } - if ((access_flags & kAccInterface) != 0) { - result += "interface "; - } - if ((access_flags & kAccTransient) != 0) { - result += "transient "; - } - if ((access_flags & kAccVolatile) != 0) { - result += "volatile "; - } - if ((access_flags & kAccSynchronized) != 0) { - result += "synchronized "; - } - return result; -} - std::string PrettySize(int64_t byte_count) { // The byte thresholds at which we display amounts. A byte count is displayed // in unit U when kUnitThresholds[U] <= bytes < kUnitThresholds[U+1]. diff --git a/runtime/utils.h b/runtime/utils.h index 7402c12280..abdafcc9f7 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -82,10 +82,6 @@ void AppendPrettyDescriptor(const char* descriptor, std::string* result); std::string PrettyDescriptor(const char* descriptor); std::string PrettyDescriptor(Primitive::Type type); -// Returns a human-readable version of the Java part of the access flags, e.g., "private static " -// (note the trailing whitespace). -std::string PrettyJavaAccessFlags(uint32_t access_flags); - // Returns a human-readable size string such as "1MB". std::string PrettySize(int64_t size_in_bytes); diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index 4e45128420..202380de8f 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -218,6 +218,10 @@ class VdexFile { ArrayRef<const uint8_t> GetQuickenedInfoOf(const DexFile& dex_file, uint32_t dex_method_idx) const; + bool HasDexSection() const { + return GetHeader().GetDexSize() != 0; + } + private: uint32_t GetQuickeningInfoTableOffset(const uint8_t* source_dex_begin) const; @@ -235,10 +239,6 @@ class VdexFile { uint32_t num_method_ids, const ArrayRef<const uint8_t>& quickening_info) const; - bool HasDexSection() const { - return GetHeader().GetDexSize() != 0; - } - bool ContainsDexFile(const DexFile& dex_file) const; const uint8_t* DexBegin() const { diff --git a/runtime/verifier/reg_type-inl.h b/runtime/verifier/reg_type-inl.h index f719782727..9e12d636d4 100644 --- a/runtime/verifier/reg_type-inl.h +++ b/runtime/verifier/reg_type-inl.h @@ -48,9 +48,6 @@ inline bool RegType::CanAccess(const RegType& other) const { inline bool RegType::CanAccessMember(ObjPtr<mirror::Class> klass, uint32_t access_flags) const { DCHECK(IsReferenceTypes()); - if ((access_flags & kAccPublic) != 0) { - return true; - } if (IsNull()) { return true; } diff --git a/test/530-checker-lse/src/Main.java b/test/530-checker-lse/src/Main.java index 98838c5089..f6332b5503 100644 --- a/test/530-checker-lse/src/Main.java +++ b/test/530-checker-lse/src/Main.java @@ -398,6 +398,7 @@ public class Main { /// CHECK-START: int Main.test15() load_store_elimination (after) /// CHECK: <<Const2:i\d+>> IntConstant 2 /// CHECK: StaticFieldSet + /// CHECK: StaticFieldSet /// CHECK-NOT: StaticFieldGet /// CHECK: Return [<<Const2>>] @@ -772,127 +773,6 @@ public class Main { return obj; } - /// CHECK-START: void Main.testStoreStore2(TestClass2) load_store_elimination (before) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - - /// CHECK-START: void Main.testStoreStore2(TestClass2) load_store_elimination (after) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK-NOT: InstanceFieldSet - - private static void testStoreStore2(TestClass2 obj) { - obj.i = 41; - obj.j = 42; - obj.i = 43; - obj.j = 44; - } - - /// CHECK-START: void Main.testStoreStore3(TestClass2, boolean) load_store_elimination (before) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - - /// CHECK-START: void Main.testStoreStore3(TestClass2, boolean) load_store_elimination (after) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldSet - /// CHECK-NOT: InstanceFieldSet - - private static void testStoreStore3(TestClass2 obj, boolean flag) { - obj.i = 41; - obj.j = 42; // redundant since it's overwritten in both branches below. - if (flag) { - obj.j = 43; - } else { - obj.j = 44; - } - } - - /// CHECK-START: void Main.testStoreStore4() load_store_elimination (before) - /// CHECK: StaticFieldSet - /// CHECK: StaticFieldSet - - /// CHECK-START: void Main.testStoreStore4() load_store_elimination (after) - /// CHECK: StaticFieldSet - /// CHECK-NOT: StaticFieldSet - - private static void testStoreStore4() { - TestClass.si = 61; - TestClass.si = 62; - } - - /// CHECK-START: int Main.testStoreStore5(TestClass2, TestClass2) load_store_elimination (before) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldGet - /// CHECK: InstanceFieldSet - - /// CHECK-START: int Main.testStoreStore5(TestClass2, TestClass2) load_store_elimination (after) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldGet - /// CHECK: InstanceFieldSet - - private static int testStoreStore5(TestClass2 obj1, TestClass2 obj2) { - obj1.i = 71; // This store is needed since obj2.i may load from it. - int i = obj2.i; - obj1.i = 72; - return i; - } - - /// CHECK-START: int Main.testStoreStore6(TestClass2, TestClass2) load_store_elimination (before) - /// CHECK: InstanceFieldSet - /// CHECK: InstanceFieldGet - /// CHECK: InstanceFieldSet - - /// CHECK-START: int Main.testStoreStore6(TestClass2, TestClass2) load_store_elimination (after) - /// CHECK-NOT: InstanceFieldSet - /// CHECK: InstanceFieldGet - /// CHECK: InstanceFieldSet - - private static int testStoreStore6(TestClass2 obj1, TestClass2 obj2) { - obj1.i = 81; // This store is not needed since obj2.j cannot load from it. - int j = obj2.j; - obj1.i = 82; - return j; - } - - /// CHECK-START: int Main.testNoSideEffects(int[]) load_store_elimination (before) - /// CHECK: ArraySet - /// CHECK: ArraySet - /// CHECK: ArraySet - /// CHECK: ArrayGet - - /// CHECK-START: int Main.testNoSideEffects(int[]) load_store_elimination (after) - /// CHECK: ArraySet - /// CHECK: ArraySet - /// CHECK-NOT: ArraySet - /// CHECK-NOT: ArrayGet - - private static int testNoSideEffects(int[] array) { - array[0] = 101; - array[1] = 102; - int bitCount = Integer.bitCount(0x3456); - array[1] = 103; - return array[0] + bitCount; - } - - /// CHECK-START: void Main.testThrow(TestClass2, java.lang.Exception) load_store_elimination (before) - /// CHECK: InstanceFieldSet - /// CHECK: Throw - - /// CHECK-START: void Main.testThrow(TestClass2, java.lang.Exception) load_store_elimination (after) - /// CHECK: InstanceFieldSet - /// CHECK: Throw - - // Make sure throw keeps the store. - private static void testThrow(TestClass2 obj, Exception e) throws Exception { - obj.i = 55; - throw e; - } - /// CHECK-START: int Main.testStoreStoreWithDeoptimize(int[]) load_store_elimination (before) /// CHECK: NewInstance /// CHECK: InstanceFieldSet @@ -934,6 +814,23 @@ public class Main { return arr[0] + arr[1] + arr[2] + arr[3]; } + /// CHECK-START: int Main.testNoSideEffects(int[]) load_store_elimination (before) + /// CHECK: ArraySet + /// CHECK: ArraySet + /// CHECK: ArrayGet + + /// CHECK-START: int Main.testNoSideEffects(int[]) load_store_elimination (after) + /// CHECK: ArraySet + /// CHECK: ArraySet + /// CHECK-NOT: ArrayGet + + private static int testNoSideEffects(int[] array) { + array[0] = 101; + int bitCount = Integer.bitCount(0x3456); + array[1] = array[0] + 1; + return array[0] + bitCount; + } + /// CHECK-START: double Main.getCircleArea(double, boolean) load_store_elimination (before) /// CHECK: NewInstance @@ -1208,46 +1105,16 @@ public class Main { assertIntEquals(testStoreStore().i, 41); assertIntEquals(testStoreStore().j, 43); + assertIntEquals(testStoreStoreWithDeoptimize(new int[4]), 4); assertIntEquals(testExitMerge(true), 2); assertIntEquals(testExitMerge2(true), 2); assertIntEquals(testExitMerge2(false), 2); - TestClass2 testclass2 = new TestClass2(); - testStoreStore2(testclass2); - assertIntEquals(testclass2.i, 43); - assertIntEquals(testclass2.j, 44); - - testStoreStore3(testclass2, true); - assertIntEquals(testclass2.i, 41); - assertIntEquals(testclass2.j, 43); - testStoreStore3(testclass2, false); - assertIntEquals(testclass2.i, 41); - assertIntEquals(testclass2.j, 44); - - testStoreStore4(); - assertIntEquals(TestClass.si, 62); - - int ret = testStoreStore5(testclass2, testclass2); - assertIntEquals(testclass2.i, 72); - assertIntEquals(ret, 71); - - testclass2.j = 88; - ret = testStoreStore6(testclass2, testclass2); - assertIntEquals(testclass2.i, 82); - assertIntEquals(ret, 88); - - ret = testNoSideEffects(iarray); + int ret = testNoSideEffects(iarray); assertIntEquals(iarray[0], 101); - assertIntEquals(iarray[1], 103); + assertIntEquals(iarray[1], 102); assertIntEquals(ret, 108); - - try { - testThrow(testclass2, new Exception()); - } catch (Exception e) {} - assertIntEquals(testclass2.i, 55); - - assertIntEquals(testStoreStoreWithDeoptimize(new int[4]), 4); } static boolean sFlag; diff --git a/test/608-checker-unresolved-lse/src/Main.java b/test/608-checker-unresolved-lse/src/Main.java index a39dd51bdf..c6f8854b49 100644 --- a/test/608-checker-unresolved-lse/src/Main.java +++ b/test/608-checker-unresolved-lse/src/Main.java @@ -88,6 +88,7 @@ public class Main extends MissingSuperClass { /// CHECK-START: void Main.staticFieldTest() load_store_elimination (after) /// CHECK: StaticFieldSet + /// CHECK: StaticFieldSet /// CHECK: UnresolvedStaticFieldGet public static void staticFieldTest() { // Ensure Foo is initialized. diff --git a/test/639-checker-code-sinking/expected.txt b/test/639-checker-code-sinking/expected.txt index 5d4833aca8..52e756c231 100644 --- a/test/639-checker-code-sinking/expected.txt +++ b/test/639-checker-code-sinking/expected.txt @@ -1,3 +1,3 @@ 0 class java.lang.Object -42 +43 diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java index a1c30f7b4e..7496925adc 100644 --- a/test/639-checker-code-sinking/src/Main.java +++ b/test/639-checker-code-sinking/src/Main.java @@ -337,7 +337,7 @@ public class Main { public static void testStoreStore(boolean doThrow) { Main m = new Main(); m.intField = 42; - m.intField2 = 43; + m.intField = 43; if (doThrow) { throw new Error(m.$opt$noinline$toString()); } @@ -349,7 +349,6 @@ public class Main { volatile int volatileField; int intField; - int intField2; Object objectField; static boolean doThrow; static boolean doLoop; diff --git a/test/674-hiddenapi/api-blacklist.txt b/test/674-hiddenapi/api-blacklist.txt new file mode 100644 index 0000000000..d43360c62f --- /dev/null +++ b/test/674-hiddenapi/api-blacklist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorBlacklist;-><init>()V +LParentClass;->fieldPublicBlacklist:I +LParentClass;->fieldPackageBlacklist:I +LParentClass;->fieldProtectedBlacklist:I +LParentClass;->fieldPrivateBlacklist:I +LParentClass;->fieldPublicStaticBlacklist:I +LParentClass;->fieldPackageStaticBlacklist:I +LParentClass;->fieldProtectedStaticBlacklist:I +LParentClass;->fieldPrivateStaticBlacklist:I +LParentClass;->methodPublicBlacklist()I +LParentClass;->methodPackageBlacklist()I +LParentClass;->methodProtectedBlacklist()I +LParentClass;->methodPrivateBlacklist()I +LParentClass;->methodPublicStaticBlacklist()I +LParentClass;->methodPackageStaticBlacklist()I +LParentClass;->methodProtectedStaticBlacklist()I +LParentClass;->methodPrivateStaticBlacklist()I +LParentClass;-><init>(IC)V +LParentClass;-><init>(FC)V +LParentClass;-><init>(JC)V +LParentClass;-><init>(DC)V +LParentInterface;->fieldPublicStaticBlacklist:I +LParentInterface;->methodPublicBlacklist()I +LParentInterface;->methodPublicStaticBlacklist()I +LParentInterface;->methodPublicDefaultBlacklist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/api-dark-greylist.txt b/test/674-hiddenapi/api-dark-greylist.txt new file mode 100644 index 0000000000..d0f35f64bc --- /dev/null +++ b/test/674-hiddenapi/api-dark-greylist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorDarkGreylist;-><init>()V +LParentClass;->fieldPublicDarkGreylist:I +LParentClass;->fieldPackageDarkGreylist:I +LParentClass;->fieldProtectedDarkGreylist:I +LParentClass;->fieldPrivateDarkGreylist:I +LParentClass;->fieldPublicStaticDarkGreylist:I +LParentClass;->fieldPackageStaticDarkGreylist:I +LParentClass;->fieldProtectedStaticDarkGreylist:I +LParentClass;->fieldPrivateStaticDarkGreylist:I +LParentClass;->methodPublicDarkGreylist()I +LParentClass;->methodPackageDarkGreylist()I +LParentClass;->methodProtectedDarkGreylist()I +LParentClass;->methodPrivateDarkGreylist()I +LParentClass;->methodPublicStaticDarkGreylist()I +LParentClass;->methodPackageStaticDarkGreylist()I +LParentClass;->methodProtectedStaticDarkGreylist()I +LParentClass;->methodPrivateStaticDarkGreylist()I +LParentClass;-><init>(IB)V +LParentClass;-><init>(FB)V +LParentClass;-><init>(JB)V +LParentClass;-><init>(DB)V +LParentInterface;->fieldPublicStaticDarkGreylist:I +LParentInterface;->methodPublicDarkGreylist()I +LParentInterface;->methodPublicStaticDarkGreylist()I +LParentInterface;->methodPublicDefaultDarkGreylist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/api-light-greylist.txt b/test/674-hiddenapi/api-light-greylist.txt new file mode 100644 index 0000000000..2809025cfd --- /dev/null +++ b/test/674-hiddenapi/api-light-greylist.txt @@ -0,0 +1,25 @@ +LNullaryConstructorLightGreylist;-><init>()V +LParentClass;->fieldPublicLightGreylist:I +LParentClass;->fieldPackageLightGreylist:I +LParentClass;->fieldProtectedLightGreylist:I +LParentClass;->fieldPrivateLightGreylist:I +LParentClass;->fieldPublicStaticLightGreylist:I +LParentClass;->fieldPackageStaticLightGreylist:I +LParentClass;->fieldProtectedStaticLightGreylist:I +LParentClass;->fieldPrivateStaticLightGreylist:I +LParentClass;->methodPublicLightGreylist()I +LParentClass;->methodPackageLightGreylist()I +LParentClass;->methodProtectedLightGreylist()I +LParentClass;->methodPrivateLightGreylist()I +LParentClass;->methodPublicStaticLightGreylist()I +LParentClass;->methodPackageStaticLightGreylist()I +LParentClass;->methodProtectedStaticLightGreylist()I +LParentClass;->methodPrivateStaticLightGreylist()I +LParentClass;-><init>(IZ)V +LParentClass;-><init>(FZ)V +LParentClass;-><init>(JZ)V +LParentClass;-><init>(DZ)V +LParentInterface;->fieldPublicStaticLightGreylist:I +LParentInterface;->methodPublicLightGreylist()I +LParentInterface;->methodPublicStaticLightGreylist()I +LParentInterface;->methodPublicDefaultLightGreylist()I
\ No newline at end of file diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build new file mode 100644 index 0000000000..330a6def29 --- /dev/null +++ b/test/674-hiddenapi/build @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Copyright 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# Build the jars twice. First with applying hiddenapi, creating a boot jar, then +# a second time without to create a normal jar. We need to do this because we +# want to load the jar once as an app module and once as a member of the boot +# class path. The DexFileVerifier would fail on the former as it does not allow +# hidden API access flags in dex files. DexFileVerifier is not invoked on boot +# class path dex files, so the boot jar loads fine in the latter case. + +export USE_HIDDENAPI=true +./default-build "$@" + +# Move the jar file into the resource folder to be bundled with the test. +mkdir res +mv ${TEST_NAME}.jar res/boot.jar + +# Clear all intermediate files otherwise default-build would either skip +# compilation or fail rebuilding. +rm -rf classes* + +export USE_HIDDENAPI=false +./default-build "$@" diff --git a/test/674-hiddenapi/check b/test/674-hiddenapi/check new file mode 100644 index 0000000000..c319a0ae97 --- /dev/null +++ b/test/674-hiddenapi/check @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Remove pid and date from the log messages. +grep -vE '^dalvikvm(32|64) E [^]]+]' "$2" \ + | grep -v JNI_OnLoad \ + | grep -v JNI_OnUnload \ + > "$2.tmp" + +./default-check "$1" "$2.tmp" diff --git a/test/674-hiddenapi/expected.txt b/test/674-hiddenapi/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/674-hiddenapi/expected.txt diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..baff6f758d --- /dev/null +++ b/test/674-hiddenapi/hiddenapi.cc @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "class_linker.h" +#include "dex/art_dex_file_loader.h" +#include "jni.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread.h" +#include "ti-agent/scoped_utf_chars.h" + +namespace art { +namespace Test674HiddenApi { + +extern "C" JNIEXPORT void JNICALL Java_Main_init(JNIEnv*, jclass) { + Runtime::Current()->SetHiddenApiChecksEnabled(true); + Runtime::Current()->SetDedupeHiddenApiWarnings(false); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader( + JNIEnv* env, jclass, jstring jpath) { + ScopedUtfChars utf(env, jpath); + const char* path = utf.c_str(); + if (path == nullptr) { + return; + } + + ArtDexFileLoader dex_loader; + std::string error_msg; + std::vector<std::unique_ptr<const DexFile>> dex_files; + if (!dex_loader.Open(path, + path, + /* verify */ false, + /* verify_checksum */ true, + &error_msg, + &dex_files)) { + LOG(FATAL) << "Could not open " << path << " for boot classpath extension: " << error_msg; + UNREACHABLE(); + } + + ScopedObjectAccess soa(Thread::Current()); + for (std::unique_ptr<const DexFile>& dex_file : dex_files) { + Runtime::Current()->GetClassLinker()->AppendToBootClassPath( + Thread::Current(), *dex_file.release()); + } +} + +static jobject NewInstance(JNIEnv* env, jclass klass) { + jmethodID constructor = env->GetMethodID(klass, "<init>", "()V"); + if (constructor == NULL) { + return NULL; + } + return env->NewObject(klass, constructor); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canGetField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + if (is_static) { + env->GetStaticIntField(klass, field); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->GetIntField(obj, field); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canSetField( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I") + : env->GetFieldID(klass, utf_name.c_str(), "I"); + if (field == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + if (is_static) { + env->SetStaticIntField(klass, field, 42); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->SetIntField(obj, field, 42); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverMethod( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodA( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + if (is_static) { + env->CallStaticIntMethodA(klass, method, nullptr); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->CallIntMethodA(obj, method, nullptr); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeMethodV( + JNIEnv* env, jclass, jclass klass, jstring name, jboolean is_static) { + ScopedUtfChars utf_name(env, name); + jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I") + : env->GetMethodID(klass, utf_name.c_str(), "()I"); + if (method == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + if (is_static) { + env->CallStaticIntMethod(klass, method); + } else { + jobject obj = NewInstance(env, klass); + if (obj == NULL) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + env->CallIntMethod(obj, method); + } + + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +static constexpr size_t kConstructorSignatureLength = 5; // e.g. (IZ)V +static constexpr size_t kNumConstructorArgs = kConstructorSignatureLength - 3; + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canDiscoverConstructor( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorA( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + // CheckJNI won't allow out-of-range values, so just zero everything. + CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength); + size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs; + jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size)); + memset(initargs, 0, initargs_size); + + env->NewObjectA(klass, constructor, initargs); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_JNI_canInvokeConstructorV( + JNIEnv* env, jclass, jclass klass, jstring args) { + ScopedUtfChars utf_args(env, args); + jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str()); + if (constructor == NULL) { + env->ExceptionClear(); + return JNI_FALSE; + } + + // CheckJNI won't allow out-of-range values, so just zero everything. + CHECK_EQ(strlen(utf_args.c_str()), kConstructorSignatureLength); + size_t initargs_size = sizeof(jvalue) * kNumConstructorArgs; + jvalue *initargs = reinterpret_cast<jvalue*>(alloca(initargs_size)); + memset(initargs, 0, initargs_size); + + static_assert(kNumConstructorArgs == 2, "Change the varargs below if you change the constant"); + env->NewObject(klass, constructor, initargs[0], initargs[1]); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +extern "C" JNIEXPORT jint JNICALL Java_Reflection_getHiddenApiAccessFlags(JNIEnv*, jclass) { + return static_cast<jint>(kAccHiddenApiBits); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_ChildClass_hasPendingWarning(JNIEnv*, jclass) { + return Runtime::Current()->HasPendingHiddenApiWarning(); +} + +extern "C" JNIEXPORT void JNICALL Java_ChildClass_clearWarning(JNIEnv*, jclass) { + Runtime::Current()->SetPendingHiddenApiWarning(false); +} + +} // namespace Test674HiddenApi +} // namespace art diff --git a/test/674-hiddenapi/info.txt b/test/674-hiddenapi/info.txt new file mode 100644 index 0000000000..25ac6ae78f --- /dev/null +++ b/test/674-hiddenapi/info.txt @@ -0,0 +1,15 @@ +Test whether hidden API access flags are being enforced. The test is composed of +two JARs. The first (parent) defines methods and fields and the second (child) +tries to access them with reflection/JNI or link against them. Note that the +first is compiled twice - once with and once without hidden access flags. + +The test then proceeds to exercise the following combinations of class loading: +(a) Both parent and child dex loaded with PathClassLoader, parent's class loader + is the child's class loader's parent. Access flags should not be enforced as + the parent does not belong to boot class path. +(b) Parent is appended to boot class path, child is loaded with PathClassLoader. + In this situation child should not be able to access hidden methods/fields + of the parent. +(c) Both parent and child are appended to boot class path. Restrictions should + not apply as hidden APIs are accessible within the boundaries of the boot + class path. diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java new file mode 100644 index 0000000000..a808e946a9 --- /dev/null +++ b/test/674-hiddenapi/src-art/Main.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dalvik.system.InMemoryDexClassLoader; +import dalvik.system.PathClassLoader; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + prepareNativeLibFileName(args[0]); + + // Enable hidden API checks in case they are disabled by default. + init(); + + // Run test with both parent and child dex files loaded with class loaders. + // The expectation is that hidden members in parent should be visible to + // the child. + doTest(false, false); + doUnloading(); + + // Now append parent dex file to boot class path and run again. This time + // the child dex file should not be able to access private APIs of the parent. + appendToBootClassLoader(DEX_PARENT_BOOT); + doTest(true, false); + doUnloading(); + + // And finally append to child to boot class path as well. With both in the + // boot class path, access should be granted. + appendToBootClassLoader(DEX_CHILD); + doTest(true, true); + doUnloading(); + } + + private static void doTest(boolean parentInBoot, boolean childInBoot) throws Exception { + // Load parent dex if it is not in boot class path. + ClassLoader parentLoader = null; + if (parentInBoot) { + parentLoader = BOOT_CLASS_LOADER; + } else { + parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader()); + } + + // Load child dex if it is not in boot class path. + ClassLoader childLoader = null; + if (childInBoot) { + if (parentLoader != BOOT_CLASS_LOADER) { + throw new IllegalStateException( + "DeclaringClass must be in parent class loader of CallingClass"); + } + childLoader = BOOT_CLASS_LOADER; + } else { + childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader); + } + + // Create a unique copy of the native library. Each shared library can only + // be loaded once, but for some reason even classes from a class loader + // cannot register their native methods against symbols in a shared library + // loaded by their parent class loader. + String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot); + + // Invoke ChildClass.runTest + Class.forName("ChildClass", true, childLoader) + .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE) + .invoke(null, nativeLibCopy, parentInBoot, childInBoot); + } + + // Routine which tries to figure out the absolute path of our native library. + private static void prepareNativeLibFileName(String arg) throws Exception { + String libName = System.mapLibraryName(arg); + Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths"); + libPathsMethod.setAccessible(true); + String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime()); + nativeLibFileName = null; + for (String p : libPaths) { + String candidate = p + libName; + if (new File(candidate).exists()) { + nativeLibFileName = candidate; + break; + } + } + if (nativeLibFileName == null) { + throw new IllegalStateException("Didn't find " + libName + " in " + + Arrays.toString(libPaths)); + } + } + + // Helper to read dex file into memory. + private static ByteBuffer readDexFile(String jarFileName) throws Exception { + ZipFile zip = new ZipFile(new File(jarFileName)); + ZipEntry entry = zip.getEntry("classes.dex"); + InputStream is = zip.getInputStream(entry); + int offset = 0; + int size = (int) entry.getSize(); + ByteBuffer buffer = ByteBuffer.allocate(size); + while (is.available() > 0) { + is.read(buffer.array(), offset, size - offset); + } + is.close(); + zip.close(); + return buffer; + } + + // Copy native library to a new file with a unique name so it does not conflict + // with other loaded instance of the same binary file. + private static String createNativeLibCopy(boolean parentInBoot, boolean childInBoot) + throws Exception { + String tempFileName = System.mapLibraryName( + "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0")); + File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName); + Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath()); + return tempFile.getAbsolutePath(); + } + + private static void doUnloading() { + // Do multiple GCs to prevent rare flakiness if some other thread is keeping the + // classloader live. + for (int i = 0; i < 5; ++i) { + Runtime.getRuntime().gc(); + } + } + + private static String nativeLibFileName; + + private static final String DEX_PARENT = + new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath(); + private static final String DEX_PARENT_BOOT = + new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath(); + private static final String DEX_CHILD = + new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath(); + + private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader(); + + private static native void appendToBootClassLoader(String dexPath); + private static native void init(); +} diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java new file mode 100644 index 0000000000..babd88359b --- /dev/null +++ b/test/674-hiddenapi/src-ex/ChildClass.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVisitor; + +public class ChildClass { + enum PrimitiveType { + TInteger('I', Integer.TYPE, Integer.valueOf(0)), + TLong('J', Long.TYPE, Long.valueOf(0)), + TFloat('F', Float.TYPE, Float.valueOf(0)), + TDouble('D', Double.TYPE, Double.valueOf(0)), + TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)), + TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)), + TShort('S', Short.TYPE, Short.valueOf((short) 0)), + TCharacter('C', Character.TYPE, Character.valueOf('0')); + + PrimitiveType(char shorty, Class klass, Object value) { + mShorty = shorty; + mClass = klass; + mDefaultValue = value; + } + + public char mShorty; + public Class mClass; + public Object mDefaultValue; + } + + enum Hiddenness { + Whitelist(PrimitiveType.TShort), + LightGreylist(PrimitiveType.TBoolean), + DarkGreylist(PrimitiveType.TByte), + Blacklist(PrimitiveType.TCharacter); + + Hiddenness(PrimitiveType type) { mAssociatedType = type; } + public PrimitiveType mAssociatedType; + } + + enum Visibility { + Public(PrimitiveType.TInteger), + Package(PrimitiveType.TFloat), + Protected(PrimitiveType.TLong), + Private(PrimitiveType.TDouble); + + Visibility(PrimitiveType type) { mAssociatedType = type; } + public PrimitiveType mAssociatedType; + } + + enum Behaviour { + Granted, + Warning, + Denied, + } + + private static final boolean booleanValues[] = new boolean[] { false, true }; + + public static void runTest(String libFileName, boolean expectedParentInBoot, + boolean expectedChildInBoot) throws Exception { + System.load(libFileName); + + // Check expectations about loading into boot class path. + isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null); + if (isParentInBoot != expectedParentInBoot) { + throw new RuntimeException("Expected ParentClass " + + (expectedParentInBoot ? "" : "not ") + "in boot class path"); + } + isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null); + if (isChildInBoot != expectedChildInBoot) { + throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") + + "in boot class path"); + } + + boolean isSameBoot = (isParentInBoot == isChildInBoot); + + // Run meaningful combinations of access flags. + for (Hiddenness hiddenness : Hiddenness.values()) { + final Behaviour expected; + if (isSameBoot || hiddenness == Hiddenness.Whitelist) { + expected = Behaviour.Granted; + } else if (hiddenness == Hiddenness.Blacklist) { + expected = Behaviour.Denied; + } else { + expected = Behaviour.Warning; + } + + for (boolean isStatic : booleanValues) { + String suffix = (isStatic ? "Static" : "") + hiddenness.name(); + + for (Visibility visibility : Visibility.values()) { + // Test reflection and JNI on methods and fields + for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) { + String baseName = visibility.name() + suffix; + checkField(klass, "field" + baseName, isStatic, visibility, expected); + checkMethod(klass, "method" + baseName, isStatic, visibility, expected); + } + + // Check whether one can use a class constructor. + checkConstructor(ParentClass.class, visibility, hiddenness, expected); + + // Check whether you can use an interface default method. + String name = "method" + visibility.name() + "Default" + hiddenness.name(); + checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected); + } + + // Test whether static linking succeeds. + checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected); + checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected); + checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected); + } + + // Check whether Class.newInstance succeeds. + checkNullaryConstructor(Class.forName("NullaryConstructor" + hiddenness.name()), expected); + } + } + + private static void checkField(Class<?> klass, String name, boolean isStatic, + Visibility visibility, Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + if (klass.isInterface() && (!isStatic || !isPublic)) { + // Interfaces only have public static fields. + return; + } + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredField(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, true, "getDeclaredField()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredFields(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, true, "getDeclaredFields()", canDiscover); + } + + if (Reflection.canDiscoverWithGetField(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, true, "getField()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetFields(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, true, "getFields()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) { + throwDiscoveryException(klass, name, true, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test that modifiers are unaffected. + + if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, true); + } + + // Test getters and setters when meaningful. + + clearWarning(); + if (!Reflection.canGetField(klass, name)) { + throwAccessException(klass, name, true, "Field.getInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.getInt()", setsWarning); + } + + clearWarning(); + if (!Reflection.canSetField(klass, name)) { + throwAccessException(klass, name, true, "Field.setInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.setInt()", setsWarning); + } + + clearWarning(); + if (!JNI.canGetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "getIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "getIntField", setsWarning); + } + + clearWarning(); + if (!JNI.canSetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "setIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "setIntField", setsWarning); + } + } + + private static void checkMethod(Class<?> klass, String name, boolean isStatic, + Visibility visibility, Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + if (klass.isInterface() && !isPublic) { + // All interface members are public. + return; + } + + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredMethod(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, false, "getDeclaredMethod()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredMethods(klass, name) != canDiscover) { + throwDiscoveryException(klass, name, false, "getDeclaredMethods()", canDiscover); + } + + if (Reflection.canDiscoverWithGetMethod(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, false, "getMethod()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetMethods(klass, name) != (canDiscover && isPublic)) { + throwDiscoveryException(klass, name, false, "getMethods()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) { + throwDiscoveryException(klass, name, false, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test that modifiers are unaffected. + + if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, false); + } + + // Test whether we can invoke the method. This skips non-static interface methods. + + if (!klass.isInterface() || isStatic) { + clearWarning(); + if (!Reflection.canInvokeMethod(klass, name)) { + throwAccessException(klass, name, false, "invoke()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "invoke()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeMethodA(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodA"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodA()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeMethodV(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodV"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodV()", setsWarning); + } + } + } + + private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness, + Behaviour behaviour) throws Exception { + + boolean isPublic = (visibility == Visibility.Public); + String signature = "(" + visibility.mAssociatedType.mShorty + + hiddenness.mAssociatedType.mShorty + ")V"; + String fullName = "<init>" + signature; + Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass, + hiddenness.mAssociatedType.mClass }; + Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue, + hiddenness.mAssociatedType.mDefaultValue }; + + boolean canDiscover = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + // Test discovery with reflection. + + if (Reflection.canDiscoverWithGetDeclaredConstructor(klass, args) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "getDeclaredConstructor()", canDiscover); + } + + if (Reflection.canDiscoverWithGetDeclaredConstructors(klass, args) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "getDeclaredConstructors()", canDiscover); + } + + if (Reflection.canDiscoverWithGetConstructor(klass, args) != (canDiscover && isPublic)) { + throwDiscoveryException( + klass, fullName, false, "getConstructor()", (canDiscover && isPublic)); + } + + if (Reflection.canDiscoverWithGetConstructors(klass, args) != (canDiscover && isPublic)) { + throwDiscoveryException( + klass, fullName, false, "getConstructors()", (canDiscover && isPublic)); + } + + // Test discovery with JNI. + + if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) { + throwDiscoveryException(klass, fullName, false, "JNI", canDiscover); + } + + // Finish here if we could not discover the field. + + if (!canDiscover) { + return; + } + + // Test whether we can invoke the constructor. + + clearWarning(); + if (!Reflection.canInvokeConstructor(klass, args, initargs)) { + throwAccessException(klass, fullName, false, "invoke()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "invoke()", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeConstructorA(klass, signature)) { + throwAccessException(klass, fullName, false, "NewObjectA"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "NewObjectA", setsWarning); + } + + clearWarning(); + if (!JNI.canInvokeConstructorV(klass, signature)) { + throwAccessException(klass, fullName, false, "NewObjectV"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, fullName, false, "NewObjectV", setsWarning); + } + } + + private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour) + throws Exception { + boolean canAccess = (behaviour != Behaviour.Denied); + boolean setsWarning = (behaviour == Behaviour.Warning); + + clearWarning(); + if (Reflection.canUseNewInstance(klass) != canAccess) { + throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + + "be able to construct " + klass.getName() + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + if (canAccess && hasPendingWarning() != setsWarning) { + throwWarningException(klass, "nullary constructor", false, "newInstance", setsWarning); + } + } + + private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour) + throws Exception { + boolean canAccess = (behaviour != Behaviour.Denied); + boolean setsWarning = false; // we do not set the flag in verifier or at runtime + + clearWarning(); + if (Linking.canAccess(className, takesParameter) != canAccess) { + throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") + + "be able to verify " + className + "." + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + if (canAccess && hasPendingWarning() != setsWarning) { + throwWarningException( + Class.forName(className), "access", false, "static linking", setsWarning); + } + } + + private static void throwDiscoveryException(Class<?> klass, String name, boolean isField, + String fn, boolean canAccess) { + throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + + "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwAccessException(Class<?> klass, String name, boolean isField, + String fn) { + throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") + + klass.getName() + "." + name + " using " + fn + ". " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwWarningException(Class<?> klass, String name, boolean isField, + String fn, boolean setsWarning) { + throw new RuntimeException("Expected access to " + (isField ? "field " : "method ") + + klass.getName() + "." + name + " using " + fn + " to " + (setsWarning ? "" : "not ") + + "set the warning flag. " + + "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot); + } + + private static void throwModifiersException(Class<?> klass, String name, boolean isField) { + throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() + + "." + name + " to not expose hidden modifiers"); + } + + private static boolean isParentInBoot; + private static boolean isChildInBoot; + + private static native boolean hasPendingWarning(); + private static native void clearWarning(); +} diff --git a/test/674-hiddenapi/src-ex/JNI.java b/test/674-hiddenapi/src-ex/JNI.java new file mode 100644 index 0000000000..5dfb2963fa --- /dev/null +++ b/test/674-hiddenapi/src-ex/JNI.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class JNI { + public static native boolean canDiscoverField(Class<?> klass, String name, boolean isStatic); + public static native boolean canGetField(Class<?> klass, String name, boolean isStatic); + public static native boolean canSetField(Class<?> klass, String name, boolean isStatic); + + public static native boolean canDiscoverMethod(Class<?> klass, String name, boolean isStatic); + public static native boolean canInvokeMethodA(Class<?> klass, String name, boolean isStatic); + public static native boolean canInvokeMethodV(Class<?> klass, String name, boolean isStatic); + + public static native boolean canDiscoverConstructor(Class<?> klass, String signature); + public static native boolean canInvokeConstructorA(Class<?> klass, String signature); + public static native boolean canInvokeConstructorV(Class<?> klass, String signature); +} diff --git a/test/674-hiddenapi/src-ex/Linking.java b/test/674-hiddenapi/src-ex/Linking.java new file mode 100644 index 0000000000..c6735d85fe --- /dev/null +++ b/test/674-hiddenapi/src-ex/Linking.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.InvocationTargetException; + +public class Linking { + public static boolean canAccess(String className, boolean takesParameter) throws Exception { + try { + Class<?> c = Class.forName(className); + if (takesParameter) { + c.getDeclaredMethod("access", Integer.TYPE).invoke(null, 42); + } else { + c.getDeclaredMethod("access").invoke(null); + } + return true; + } catch (InvocationTargetException ex) { + if (ex.getCause() instanceof IllegalAccessError) { + return false; + } else { + throw ex; + } + } + } +} + +// INSTANCE FIELD GET + +class LinkFieldGetWhitelist { + public static int access() { + return new ParentClass().fieldPublicWhitelist; + } +} + +class LinkFieldGetLightGreylist { + public static int access() { + return new ParentClass().fieldPublicLightGreylist; + } +} + +class LinkFieldGetDarkGreylist { + public static int access() { + return new ParentClass().fieldPublicDarkGreylist; + } +} + +class LinkFieldGetBlacklist { + public static int access() { + return new ParentClass().fieldPublicBlacklist; + } +} + +// INSTANCE FIELD SET + +class LinkFieldSetWhitelist { + public static void access(int x) { + new ParentClass().fieldPublicWhitelist = x; + } +} + +class LinkFieldSetLightGreylist { + public static void access(int x) { + new ParentClass().fieldPublicLightGreylist = x; + } +} + +class LinkFieldSetDarkGreylist { + public static void access(int x) { + new ParentClass().fieldPublicDarkGreylist = x; + } +} + +class LinkFieldSetBlacklist { + public static void access(int x) { + new ParentClass().fieldPublicBlacklist = x; + } +} + +// STATIC FIELD GET + +class LinkFieldGetStaticWhitelist { + public static int access() { + return ParentClass.fieldPublicStaticWhitelist; + } +} + +class LinkFieldGetStaticLightGreylist { + public static int access() { + return ParentClass.fieldPublicStaticLightGreylist; + } +} + +class LinkFieldGetStaticDarkGreylist { + public static int access() { + return ParentClass.fieldPublicStaticDarkGreylist; + } +} + +class LinkFieldGetStaticBlacklist { + public static int access() { + return ParentClass.fieldPublicStaticBlacklist; + } +} + +// STATIC FIELD SET + +class LinkFieldSetStaticWhitelist { + public static void access(int x) { + ParentClass.fieldPublicStaticWhitelist = x; + } +} + +class LinkFieldSetStaticLightGreylist { + public static void access(int x) { + ParentClass.fieldPublicStaticLightGreylist = x; + } +} + +class LinkFieldSetStaticDarkGreylist { + public static void access(int x) { + ParentClass.fieldPublicStaticDarkGreylist = x; + } +} + +class LinkFieldSetStaticBlacklist { + public static void access(int x) { + ParentClass.fieldPublicStaticBlacklist = x; + } +} + +// INVOKE INSTANCE METHOD + +class LinkMethodWhitelist { + public static int access() { + return new ParentClass().methodPublicWhitelist(); + } +} + +class LinkMethodLightGreylist { + public static int access() { + return new ParentClass().methodPublicLightGreylist(); + } +} + +class LinkMethodDarkGreylist { + public static int access() { + return new ParentClass().methodPublicDarkGreylist(); + } +} + +class LinkMethodBlacklist { + public static int access() { + return new ParentClass().methodPublicBlacklist(); + } +} + +// INVOKE STATIC METHOD + +class LinkMethodStaticWhitelist { + public static int access() { + return ParentClass.methodPublicStaticWhitelist(); + } +} + +class LinkMethodStaticLightGreylist { + public static int access() { + return ParentClass.methodPublicStaticLightGreylist(); + } +} + +class LinkMethodStaticDarkGreylist { + public static int access() { + return ParentClass.methodPublicStaticDarkGreylist(); + } +} + +class LinkMethodStaticBlacklist { + public static int access() { + return ParentClass.methodPublicStaticBlacklist(); + } +} diff --git a/test/674-hiddenapi/src-ex/Reflection.java b/test/674-hiddenapi/src-ex/Reflection.java new file mode 100644 index 0000000000..3667e91611 --- /dev/null +++ b/test/674-hiddenapi/src-ex/Reflection.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +public class Reflection { + public static boolean canDiscoverWithGetDeclaredField(Class<?> klass, String name) { + try { + klass.getDeclaredField(name); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredFields(Class<?> klass, String name) { + for (Field f : klass.getDeclaredFields()) { + if (f.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetField(Class<?> klass, String name) { + try { + klass.getField(name); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetFields(Class<?> klass, String name) { + for (Field f : klass.getFields()) { + if (f.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canGetField(Class<?> klass, String name) { + try { + Field f = klass.getDeclaredField(name); + f.setAccessible(true); + f.getInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance()); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canSetField(Class<?> klass, String name) { + try { + Field f = klass.getDeclaredField(name); + f.setAccessible(true); + f.setInt(Modifier.isStatic(f.getModifiers()) ? null : klass.newInstance(), 42); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredMethod(Class<?> klass, String name) { + try { + klass.getDeclaredMethod(name); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredMethods(Class<?> klass, String name) { + for (Method m : klass.getDeclaredMethods()) { + if (m.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetMethod(Class<?> klass, String name) { + try { + klass.getMethod(name); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetMethods(Class<?> klass, String name) { + for (Method m : klass.getMethods()) { + if (m.getName().equals(name)) { + return true; + } + } + return false; + } + + public static boolean canInvokeMethod(Class<?> klass, String name) { + try { + Method m = klass.getDeclaredMethod(name); + m.setAccessible(true); + m.invoke(klass.isInterface() ? null : klass.newInstance()); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredConstructor(Class<?> klass, Class<?> args[]) { + try { + klass.getDeclaredConstructor(args); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetDeclaredConstructors(Class<?> klass, Class<?> args[]) { + for (Constructor c : klass.getDeclaredConstructors()) { + if (Arrays.equals(c.getParameterTypes(), args)) { + return true; + } + } + return false; + } + + public static boolean canDiscoverWithGetConstructor(Class<?> klass, Class<?> args[]) { + try { + klass.getConstructor(args); + return true; + } catch (NoSuchMethodException ex) { + return false; + } + } + + public static boolean canDiscoverWithGetConstructors(Class<?> klass, Class<?> args[]) { + for (Constructor c : klass.getConstructors()) { + if (Arrays.equals(c.getParameterTypes(), args)) { + return true; + } + } + return false; + } + + public static boolean canInvokeConstructor(Class<?> klass, Class<?> args[], Object[] initargs) { + try { + Constructor c = klass.getDeclaredConstructor(args); + c.setAccessible(true); + c.newInstance(initargs); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static boolean canUseNewInstance(Class<?> klass) throws IllegalAccessException { + try { + klass.newInstance(); + return true; + } catch (InstantiationException ex) { + return false; + } + } + + private static native int getHiddenApiAccessFlags(); + + public static boolean canObserveFieldHiddenAccessFlags(Class<?> klass, String name) + throws Exception { + return (klass.getDeclaredField(name).getModifiers() & getHiddenApiAccessFlags()) != 0; + } + + public static boolean canObserveMethodHiddenAccessFlags(Class<?> klass, String name) + throws Exception { + return (klass.getDeclaredMethod(name).getModifiers() & getHiddenApiAccessFlags()) != 0; + } + + public static boolean canObserveConstructorHiddenAccessFlags(Class<?> klass, Class<?> args[]) + throws Exception { + return (klass.getConstructor(args).getModifiers() & getHiddenApiAccessFlags()) != 0; + } +} diff --git a/test/674-hiddenapi/src/NullaryConstructorBlacklist.java b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java new file mode 100644 index 0000000000..5bf6278a77 --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorBlacklist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorBlacklist { + public NullaryConstructorBlacklist() { x = 22; } + public NullaryConstructorBlacklist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java new file mode 100644 index 0000000000..c25a767d1d --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorDarkGreylist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorDarkGreylist { + public NullaryConstructorDarkGreylist() { x = 22; } + public NullaryConstructorDarkGreylist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java new file mode 100644 index 0000000000..d5dac8b7c0 --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorLightGreylist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorLightGreylist { + public NullaryConstructorLightGreylist() { x = 22; } + public NullaryConstructorLightGreylist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/NullaryConstructorWhitelist.java b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java new file mode 100644 index 0000000000..d1019077cf --- /dev/null +++ b/test/674-hiddenapi/src/NullaryConstructorWhitelist.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class NullaryConstructorWhitelist { + public NullaryConstructorWhitelist() { x = 22; } + public NullaryConstructorWhitelist(int y) { x = y; } + protected int x; +} diff --git a/test/674-hiddenapi/src/ParentClass.java b/test/674-hiddenapi/src/ParentClass.java new file mode 100644 index 0000000000..edad02dc2c --- /dev/null +++ b/test/674-hiddenapi/src/ParentClass.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class ParentClass { + public ParentClass() {} + + // INSTANCE FIELD + + public int fieldPublicWhitelist = 211; + int fieldPackageWhitelist = 212; + protected int fieldProtectedWhitelist = 213; + private int fieldPrivateWhitelist = 214; + + public int fieldPublicLightGreylist = 221; + int fieldPackageLightGreylist = 222; + protected int fieldProtectedLightGreylist = 223; + private int fieldPrivateLightGreylist = 224; + + public int fieldPublicDarkGreylist = 231; + int fieldPackageDarkGreylist = 232; + protected int fieldProtectedDarkGreylist = 233; + private int fieldPrivateDarkGreylist = 234; + + public int fieldPublicBlacklist = 241; + int fieldPackageBlacklist = 242; + protected int fieldProtectedBlacklist = 243; + private int fieldPrivateBlacklist = 244; + + // STATIC FIELD + + public static int fieldPublicStaticWhitelist = 111; + static int fieldPackageStaticWhitelist = 112; + protected static int fieldProtectedStaticWhitelist = 113; + private static int fieldPrivateStaticWhitelist = 114; + + public static int fieldPublicStaticLightGreylist = 121; + static int fieldPackageStaticLightGreylist = 122; + protected static int fieldProtectedStaticLightGreylist = 123; + private static int fieldPrivateStaticLightGreylist = 124; + + public static int fieldPublicStaticDarkGreylist = 131; + static int fieldPackageStaticDarkGreylist = 132; + protected static int fieldProtectedStaticDarkGreylist = 133; + private static int fieldPrivateStaticDarkGreylist = 134; + + public static int fieldPublicStaticBlacklist = 141; + static int fieldPackageStaticBlacklist = 142; + protected static int fieldProtectedStaticBlacklist = 143; + private static int fieldPrivateStaticBlacklist = 144; + + // INSTANCE METHOD + + public int methodPublicWhitelist() { return 411; } + int methodPackageWhitelist() { return 412; } + protected int methodProtectedWhitelist() { return 413; } + private int methodPrivateWhitelist() { return 414; } + + public int methodPublicLightGreylist() { return 421; } + int methodPackageLightGreylist() { return 422; } + protected int methodProtectedLightGreylist() { return 423; } + private int methodPrivateLightGreylist() { return 424; } + + public int methodPublicDarkGreylist() { return 431; } + int methodPackageDarkGreylist() { return 432; } + protected int methodProtectedDarkGreylist() { return 433; } + private int methodPrivateDarkGreylist() { return 434; } + + public int methodPublicBlacklist() { return 441; } + int methodPackageBlacklist() { return 442; } + protected int methodProtectedBlacklist() { return 443; } + private int methodPrivateBlacklist() { return 444; } + + // STATIC METHOD + + public static int methodPublicStaticWhitelist() { return 311; } + static int methodPackageStaticWhitelist() { return 312; } + protected static int methodProtectedStaticWhitelist() { return 313; } + private static int methodPrivateStaticWhitelist() { return 314; } + + public static int methodPublicStaticLightGreylist() { return 321; } + static int methodPackageStaticLightGreylist() { return 322; } + protected static int methodProtectedStaticLightGreylist() { return 323; } + private static int methodPrivateStaticLightGreylist() { return 324; } + + public static int methodPublicStaticDarkGreylist() { return 331; } + static int methodPackageStaticDarkGreylist() { return 332; } + protected static int methodProtectedStaticDarkGreylist() { return 333; } + private static int methodPrivateStaticDarkGreylist() { return 334; } + + public static int methodPublicStaticBlacklist() { return 341; } + static int methodPackageStaticBlacklist() { return 342; } + protected static int methodProtectedStaticBlacklist() { return 343; } + private static int methodPrivateStaticBlacklist() { return 344; } + + // CONSTRUCTOR + + // Whitelist + public ParentClass(int x, short y) {} + ParentClass(float x, short y) {} + protected ParentClass(long x, short y) {} + private ParentClass(double x, short y) {} + + // Light greylist + public ParentClass(int x, boolean y) {} + ParentClass(float x, boolean y) {} + protected ParentClass(long x, boolean y) {} + private ParentClass(double x, boolean y) {} + + // Dark greylist + public ParentClass(int x, byte y) {} + ParentClass(float x, byte y) {} + protected ParentClass(long x, byte y) {} + private ParentClass(double x, byte y) {} + + // Blacklist + public ParentClass(int x, char y) {} + ParentClass(float x, char y) {} + protected ParentClass(long x, char y) {} + private ParentClass(double x, char y) {} +} diff --git a/test/674-hiddenapi/src/ParentInterface.java b/test/674-hiddenapi/src/ParentInterface.java new file mode 100644 index 0000000000..e36fe0e6b2 --- /dev/null +++ b/test/674-hiddenapi/src/ParentInterface.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface ParentInterface { + // STATIC FIELD + static int fieldPublicStaticWhitelist = 11; + static int fieldPublicStaticLightGreylist = 12; + static int fieldPublicStaticDarkGreylist = 13; + static int fieldPublicStaticBlacklist = 14; + + // INSTANCE METHOD + int methodPublicWhitelist(); + int methodPublicBlacklist(); + int methodPublicLightGreylist(); + int methodPublicDarkGreylist(); + + // STATIC METHOD + static int methodPublicStaticWhitelist() { return 21; } + static int methodPublicStaticLightGreylist() { return 22; } + static int methodPublicStaticDarkGreylist() { return 23; } + static int methodPublicStaticBlacklist() { return 24; } + + // DEFAULT METHOD + default int methodPublicDefaultWhitelist() { return 31; } + default int methodPublicDefaultLightGreylist() { return 32; } + default int methodPublicDefaultDarkGreylist() { return 33; } + default int methodPublicDefaultBlacklist() { return 34; } +} diff --git a/test/674-vdex-uncompress/build b/test/674-vdex-uncompress/build new file mode 100755 index 0000000000..7b1804d3e0 --- /dev/null +++ b/test/674-vdex-uncompress/build @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Uncompress and align the dex files so that dex2oat will not copy the dex +# code to the .vdex file. +./default-build "$@" --zip-compression-method store --zip-align 4 diff --git a/test/674-vdex-uncompress/expected.txt b/test/674-vdex-uncompress/expected.txt new file mode 100644 index 0000000000..d0f61f692c --- /dev/null +++ b/test/674-vdex-uncompress/expected.txt @@ -0,0 +1,2 @@ +In foo +In foo diff --git a/test/674-vdex-uncompress/info.txt b/test/674-vdex-uncompress/info.txt new file mode 100644 index 0000000000..6aa6f7b0d5 --- /dev/null +++ b/test/674-vdex-uncompress/info.txt @@ -0,0 +1,2 @@ +Test that dex2oat can compile an APK that has uncompressed dex files, +and where --input-vdex is passed. diff --git a/test/674-vdex-uncompress/run b/test/674-vdex-uncompress/run new file mode 100644 index 0000000000..edf699f842 --- /dev/null +++ b/test/674-vdex-uncompress/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex "${@}" diff --git a/test/674-vdex-uncompress/src/Main.java b/test/674-vdex-uncompress/src/Main.java new file mode 100644 index 0000000000..0a25b564fe --- /dev/null +++ b/test/674-vdex-uncompress/src/Main.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + Main() { + // Will be quickened with RETURN_VOID_NO_BARRIER. + } + + public static void main(String[] args) { + Main m = new Main(); + Object o = m; + // The call and field accesses will be quickened. + m.foo(m.a); + + // The checkcast will be quickened. + m.foo(((Main)o).a); + } + + int a; + void foo(int a) { + System.out.println("In foo"); + } +} + diff --git a/test/Android.bp b/test/Android.bp index 49a34a1246..470a68f386 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -389,6 +389,7 @@ cc_defaults { "661-oat-writer-layout/oat_writer_layout.cc", "664-aget-verifier/aget-verifier.cc", "667-jit-jni-stub/jit_jni_stub_test.cc", + "674-hiddenapi/hiddenapi.cc", "708-jit-cache-churn/jit.cc", ], shared_libs: [ diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 2cada76d90..2df0cc6fae 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -20,6 +20,7 @@ include art/build/Android.common_test.mk # Dependencies for actually running a run-test. TEST_ART_RUN_TEST_DEPENDENCIES := \ $(HOST_OUT_EXECUTABLES)/dx \ + $(HOST_OUT_EXECUTABLES)/hiddenapi \ $(HOST_OUT_EXECUTABLES)/jasmin \ $(HOST_OUT_EXECUTABLES)/smali \ $(HOST_OUT_EXECUTABLES)/dexmerger \ diff --git a/test/etc/default-build b/test/etc/default-build index 5c8257f210..4dc2393c54 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -81,6 +81,17 @@ else HAS_SRC_DEX2OAT_UNRESOLVED=false fi +if [ -f api-light-greylist.txt -o -f api-dark-greylist.txt -o -f api-blacklist.txt ]; then + HAS_HIDDENAPI_SPEC=true +else + HAS_HIDDENAPI_SPEC=false +fi + +# USE_HIDDENAPI=false run-test... will disable hiddenapi. +if [ -z "${USE_HIDDENAPI}" ]; then + USE_HIDDENAPI=true +fi + # DESUGAR=false run-test... will disable desugar. if [[ "$DESUGAR" == false ]]; then USE_DESUGAR=false @@ -321,6 +332,24 @@ function make_dexmerge() { ${DXMERGER} "$dst_file" "${dex_files_to_merge[@]}" } +function make_hiddenapi() { + local args=() + while [[ $# -gt 0 ]]; do + args+=("--dex=$1") + shift + done + if [ -f api-light-greylist.txt ]; then + args+=("--light-greylist=api-light-greylist.txt") + fi + if [ -f api-dark-greylist.txt ]; then + args+=("--dark-greylist=api-dark-greylist.txt") + fi + if [ -f api-blacklist.txt ]; then + args+=("--blacklist=api-blacklist.txt") + fi + ${HIDDENAPI} "${args[@]}" +} + # Print the directory name only if it exists. function maybe_dir() { local dirname="$1" @@ -334,6 +363,13 @@ if [ -e classes.dex ]; then exit 0 fi +# Helper function for a common test. Evaluate with $(has_mutlidex). +function has_multidex() { + echo [ ${HAS_SRC_MULTIDEX} = "true" \ + -o ${HAS_JASMIN_MULTIDEX} = "true" \ + -o ${HAS_SMALI_MULTIDEX} = "true" ] +} + if [ ${HAS_SRC_DEX2OAT_UNRESOLVED} = "true" ]; then mkdir classes mkdir classes-ex @@ -501,9 +537,18 @@ if [ ${HAS_SRC_EX} = "true" ]; then fi fi +# Apply hiddenapi on the dex files if the test has API list file(s). +if [ ${NEED_DEX} = "true" -a ${USE_HIDDENAPI} = "true" -a ${HAS_HIDDENAPI_SPEC} = "true" ]; then + if $(has_multidex); then + make_hiddenapi classes.dex classes2.dex + else + make_hiddenapi classes.dex + fi +fi + # Create a single dex jar with two dex files for multidex. if [ ${NEED_DEX} = "true" ]; then - if [ ${HAS_SRC_MULTIDEX} = "true" ] || [ ${HAS_JASMIN_MULTIDEX} = "true" ] || [ ${HAS_SMALI_MULTIDEX} = "true" ]; then + if $(has_multidex); then zip $TEST_NAME.jar classes.dex classes2.dex else zip $TEST_NAME.jar classes.dex diff --git a/test/knownfailures.json b/test/knownfailures.json index 41d976a174..27bec3e965 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -256,11 +256,6 @@ "testing deoptimizing at quick-to-interpreter bridge."] }, { - "tests": "137-cfi", - "description": ["CFI unwinding expects managed frames"], - "variant": "interpreter" - }, - { "tests": "906-iterate-heap", "description": ["Test 906 iterates the heap filtering with different", "options. No instances should be created between those", diff --git a/test/run-test b/test/run-test index 75fe15c919..a453f22e29 100755 --- a/test/run-test +++ b/test/run-test @@ -113,6 +113,12 @@ if [ -z "$ZIPALIGN" ]; then fi export ZIPALIGN +# If hiddenapi was not set by the environment variable, assume it is in +# ANDROID_HOST_OUT. +if [ -z "$HIDDENAPI" ]; then + export HIDDENAPI="${ANDROID_HOST_OUT}/bin/hiddenapi" +fi + info="info.txt" build="build" run="run" diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index fafa1afcda..e46abb6f5a 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -22,7 +22,7 @@ namespace art { -// Taken from art/runtime/modifiers.h +// Taken from art/runtime/dex/modifiers.h static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f); diff --git a/tools/cpp-define-generator/constant_class.def b/tools/cpp-define-generator/constant_class.def index 4f1d875e5a..1310103ab7 100644 --- a/tools/cpp-define-generator/constant_class.def +++ b/tools/cpp-define-generator/constant_class.def @@ -15,8 +15,8 @@ */ #if defined(DEFINE_INCLUDE_DEPENDENCIES) -#include "modifiers.h" // kAccClassIsFinalizable #include "base/bit_utils.h" // MostSignificantBit +#include "dex/modifiers.h" // kAccClassIsFinalizable #endif #define DEFINE_FLAG_OFFSET(type_name, field_name, expr) \ diff --git a/tools/cpp-define-generator/constant_globals.def b/tools/cpp-define-generator/constant_globals.def index 5018f52937..539633e0b3 100644 --- a/tools/cpp-define-generator/constant_globals.def +++ b/tools/cpp-define-generator/constant_globals.def @@ -18,8 +18,8 @@ #if defined(DEFINE_INCLUDE_DEPENDENCIES) #include <atomic> // std::memory_order_relaxed +#include "dex/modifiers.h" #include "globals.h" // art::kObjectAlignment -#include "modifiers.h" #endif DEFINE_EXPR(STD_MEMORY_ORDER_RELAXED, int32_t, std::memory_order_relaxed) |