diff options
85 files changed, 4552 insertions, 346 deletions
diff --git a/build/art.go b/build/art.go index f52c63525a..db626fd19c 100644 --- a/build/art.go +++ b/build/art.go @@ -83,20 +83,20 @@ func globalFlags(ctx android.BaseContext) ([]string, []string) { // the debug version. So make the gap consistent (and adjust for the worst). if len(ctx.AConfig().SanitizeDevice()) > 0 || len(ctx.AConfig().SanitizeHost()) > 0 { cflags = append(cflags, - "-DART_STACK_OVERFLOW_GAP_arm=8192", - "-DART_STACK_OVERFLOW_GAP_arm64=8192", - "-DART_STACK_OVERFLOW_GAP_mips=16384", - "-DART_STACK_OVERFLOW_GAP_mips64=16384", - "-DART_STACK_OVERFLOW_GAP_x86=16384", - "-DART_STACK_OVERFLOW_GAP_x86_64=20480") + "-DART_STACK_OVERFLOW_GAP_arm=8192", + "-DART_STACK_OVERFLOW_GAP_arm64=8192", + "-DART_STACK_OVERFLOW_GAP_mips=16384", + "-DART_STACK_OVERFLOW_GAP_mips64=16384", + "-DART_STACK_OVERFLOW_GAP_x86=16384", + "-DART_STACK_OVERFLOW_GAP_x86_64=20480") } else { cflags = append(cflags, - "-DART_STACK_OVERFLOW_GAP_arm=8192", - "-DART_STACK_OVERFLOW_GAP_arm64=8192", - "-DART_STACK_OVERFLOW_GAP_mips=16384", - "-DART_STACK_OVERFLOW_GAP_mips64=16384", - "-DART_STACK_OVERFLOW_GAP_x86=8192", - "-DART_STACK_OVERFLOW_GAP_x86_64=8192") + "-DART_STACK_OVERFLOW_GAP_arm=8192", + "-DART_STACK_OVERFLOW_GAP_arm64=8192", + "-DART_STACK_OVERFLOW_GAP_mips=16384", + "-DART_STACK_OVERFLOW_GAP_mips64=16384", + "-DART_STACK_OVERFLOW_GAP_x86=8192", + "-DART_STACK_OVERFLOW_GAP_x86_64=8192") } return cflags, asflags @@ -168,10 +168,10 @@ func globalDefaults(ctx android.LoadHookContext) { Cflags []string } } - Cflags []string - Asflags []string + Cflags []string + Asflags []string Sanitize struct { - Recover []string + Recover []string } } @@ -182,7 +182,7 @@ func globalDefaults(ctx android.LoadHookContext) { if envTrue(ctx, "ART_DEX_FILE_ACCESS_TRACKING") { p.Cflags = append(p.Cflags, "-DART_DEX_FILE_ACCESS_TRACKING") - p.Sanitize.Recover = []string { + p.Sanitize.Recover = []string{ "address", } } diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index d2493137fe..32f40024d3 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -31,6 +31,9 @@ namespace art { // Enables vectorization (SIMDization) in the loop optimizer. static constexpr bool kEnableVectorization = true; +// All current SIMD targets want 16-byte alignment. +static constexpr size_t kAlignedBase = 16; + // Remove the instruction from the graph. A bit more elaborate than the usual // instruction removal, since there may be a cycle in the use structure. static void RemoveFromCycle(HInstruction* instruction) { @@ -283,6 +286,9 @@ HLoopOptimization::HLoopOptimization(HGraph* graph, simplified_(false), vector_length_(0), vector_refs_(nullptr), + vector_peeling_candidate_(nullptr), + vector_runtime_test_a_(nullptr), + vector_runtime_test_b_(nullptr), vector_map_(nullptr) { } @@ -422,23 +428,6 @@ void HLoopOptimization::TraverseLoopsInnerToOuter(LoopNode* node) { // Optimization. // -bool HLoopOptimization::CanRemoveCycle() { - for (HInstruction* i : *iset_) { - // We can never remove instructions that have environment - // uses when we compile 'debuggable'. - if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { - return false; - } - // A deoptimization should never have an environment input removed. - for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { - if (use.GetUser()->GetHolder()->IsDeoptimize()) { - return false; - } - } - } - return true; -} - void HLoopOptimization::SimplifyInduction(LoopNode* node) { HBasicBlock* header = node->loop_info->GetHeader(); HBasicBlock* preheader = node->loop_info->GetPreHeader(); @@ -565,7 +554,7 @@ void HLoopOptimization::OptimizeInnerLoop(LoopNode* node) { if (kEnableVectorization) { iset_->clear(); // prepare phi induction if (TrySetSimpleLoopHeader(header) && - CanVectorize(node, body, trip_count) && + ShouldVectorize(node, body, trip_count) && TryAssignLastValue(node->loop_info, phi, preheader, /*collect_loop_uses*/ true)) { Vectorize(node, body, exit, trip_count); graph_->SetHasSIMD(true); // flag SIMD usage @@ -580,10 +569,11 @@ void HLoopOptimization::OptimizeInnerLoop(LoopNode* node) { // Intel Press, June, 2004 (http://www.aartbik.com/). // -bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count) { +bool HLoopOptimization::ShouldVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count) { // Reset vector bookkeeping. vector_length_ = 0; vector_refs_->clear(); + vector_peeling_candidate_ = nullptr; vector_runtime_test_a_ = vector_runtime_test_b_= nullptr; @@ -600,12 +590,9 @@ bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t } } - // Heuristics. Does vectorization seem profitable? - // TODO: refine - if (vector_length_ == 0) { - return false; // nothing found - } else if (0 < trip_count && trip_count < vector_length_) { - return false; // insufficient iterations + // Does vectorization seem profitable? + if (!IsVectorizationProfitable(trip_count)) { + return false; } // Data dependence analysis. Find each pair of references with same type, where @@ -645,6 +632,9 @@ bool HLoopOptimization::CanVectorize(LoopNode* node, HBasicBlock* block, int64_t } } + // Consider dynamic loop peeling for alignment. + SetPeelingCandidate(trip_count); + // Success! return true; } @@ -657,28 +647,52 @@ void HLoopOptimization::Vectorize(LoopNode* node, HBasicBlock* header = node->loop_info->GetHeader(); HBasicBlock* preheader = node->loop_info->GetPreHeader(); - // A cleanup is needed for any unknown trip count or for a known trip count - // with remainder iterations after vectorization. - bool needs_cleanup = trip_count == 0 || (trip_count % vector_length_) != 0; + // Pick a loop unrolling factor for the vector loop. + uint32_t unroll = GetUnrollingFactor(block, trip_count); + uint32_t chunk = vector_length_ * unroll; + + // A cleanup loop is needed, at least, for any unknown trip count or + // for a known trip count with remainder iterations after vectorization. + bool needs_cleanup = trip_count == 0 || (trip_count % chunk) != 0; // Adjust vector bookkeeping. iset_->clear(); // prepare phi induction bool is_simple_loop_header = TrySetSimpleLoopHeader(header); // fills iset_ DCHECK(is_simple_loop_header); + vector_header_ = header; + vector_body_ = block; + + // Generate dynamic loop peeling trip count, if needed: + // ptc = <peeling-needed-for-candidate> + HInstruction* ptc = nullptr; + if (vector_peeling_candidate_ != nullptr) { + DCHECK_LT(vector_length_, trip_count) << "dynamic peeling currently requires known trip count"; + // + // TODO: Implement this. Compute address of first access memory location and + // compute peeling factor to obtain kAlignedBase alignment. + // + needs_cleanup = true; + } - // Generate preheader: + // Generate loop control: // stc = <trip-count>; - // vtc = stc - stc % VL; + // vtc = stc - (stc - ptc) % chunk; + // i = 0; HInstruction* stc = induction_range_.GenerateTripCount(node->loop_info, graph_, preheader); HInstruction* vtc = stc; if (needs_cleanup) { - DCHECK(IsPowerOfTwo(vector_length_)); + DCHECK(IsPowerOfTwo(chunk)); + HInstruction* diff = stc; + if (ptc != nullptr) { + diff = Insert(preheader, new (global_allocator_) HSub(induc_type, stc, ptc)); + } HInstruction* rem = Insert( preheader, new (global_allocator_) HAnd(induc_type, - stc, - graph_->GetIntConstant(vector_length_ - 1))); + diff, + graph_->GetIntConstant(chunk - 1))); vtc = Insert(preheader, new (global_allocator_) HSub(induc_type, stc, rem)); } + vector_index_ = graph_->GetIntConstant(0); // Generate runtime disambiguation test: // vtc = a != b ? vtc : 0; @@ -691,16 +705,31 @@ void HLoopOptimization::Vectorize(LoopNode* node, needs_cleanup = true; } - // Generate vector loop: - // for (i = 0; i < vtc; i += VL) + // Generate dynamic peeling loop for alignment, if needed: + // for ( ; i < ptc; i += 1) + // <loop-body> + if (ptc != nullptr) { + vector_mode_ = kSequential; + GenerateNewLoop(node, + block, + graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), + vector_index_, + ptc, + graph_->GetIntConstant(1), + /*unroll*/ 1); + } + + // Generate vector loop, possibly further unrolled: + // for ( ; i < vtc; i += chunk) // <vectorized-loop-body> vector_mode_ = kVector; GenerateNewLoop(node, block, - graph_->TransformLoopForVectorization(header, block, exit), - graph_->GetIntConstant(0), + graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), + vector_index_, vtc, - graph_->GetIntConstant(vector_length_)); + graph_->GetIntConstant(vector_length_), // increment per unroll + unroll); HLoopInformation* vloop = vector_header_->GetLoopInformation(); // Generate cleanup loop, if needed: @@ -711,9 +740,10 @@ void HLoopOptimization::Vectorize(LoopNode* node, GenerateNewLoop(node, block, graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit), - vector_phi_, + vector_index_, stc, - graph_->GetIntConstant(1)); + graph_->GetIntConstant(1), + /*unroll*/ 1); } // Remove the original loop by disconnecting the body block @@ -722,8 +752,9 @@ void HLoopOptimization::Vectorize(LoopNode* node, while (!header->GetFirstInstruction()->IsGoto()) { header->RemoveInstruction(header->GetFirstInstruction()); } - // Update loop hierarchy: the old header now resides in the - // same outer loop as the old preheader. + // Update loop hierarchy: the old header now resides in the same outer loop + // as the old preheader. Note that we don't bother putting sequential + // loops back in the hierarchy at this point. header->SetLoopInformation(preheader->GetLoopInformation()); // outward node->loop_info = vloop; } @@ -733,44 +764,64 @@ void HLoopOptimization::GenerateNewLoop(LoopNode* node, HBasicBlock* new_preheader, HInstruction* lo, HInstruction* hi, - HInstruction* step) { + HInstruction* step, + uint32_t unroll) { + DCHECK(unroll == 1 || vector_mode_ == kVector); Primitive::Type induc_type = Primitive::kPrimInt; // Prepare new loop. - vector_map_->clear(); vector_preheader_ = new_preheader, vector_header_ = vector_preheader_->GetSingleSuccessor(); vector_body_ = vector_header_->GetSuccessors()[1]; - vector_phi_ = new (global_allocator_) HPhi(global_allocator_, - kNoRegNumber, - 0, - HPhi::ToPhiType(induc_type)); + HPhi* phi = new (global_allocator_) HPhi(global_allocator_, + kNoRegNumber, + 0, + HPhi::ToPhiType(induc_type)); // Generate header and prepare body. // for (i = lo; i < hi; i += step) // <loop-body> - HInstruction* cond = new (global_allocator_) HAboveOrEqual(vector_phi_, hi); - vector_header_->AddPhi(vector_phi_); + HInstruction* cond = new (global_allocator_) HAboveOrEqual(phi, hi); + vector_header_->AddPhi(phi); vector_header_->AddInstruction(cond); vector_header_->AddInstruction(new (global_allocator_) HIf(cond)); - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - bool vectorized_def = VectorizeDef(node, it.Current(), /*generate_code*/ true); - DCHECK(vectorized_def); - } - // Generate body from the instruction map, but in original program order. - HEnvironment* env = vector_header_->GetFirstInstruction()->GetEnvironment(); - for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { - auto i = vector_map_->find(it.Current()); - if (i != vector_map_->end() && !i->second->IsInBlock()) { - Insert(vector_body_, i->second); - // Deal with instructions that need an environment, such as the scalar intrinsics. - if (i->second->NeedsEnvironment()) { - i->second->CopyEnvironmentFromWithLoopPhiAdjustment(env, vector_header_); + vector_index_ = phi; + for (uint32_t u = 0; u < unroll; u++) { + // Clear map, leaving loop invariants setup during unrolling. + if (u == 0) { + vector_map_->clear(); + } else { + for (auto i = vector_map_->begin(); i != vector_map_->end(); ) { + if (i->second->IsVecReplicateScalar()) { + DCHECK(node->loop_info->IsDefinedOutOfTheLoop(i->first)); + ++i; + } else { + i = vector_map_->erase(i); + } + } + } + // Generate instruction map. + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + bool vectorized_def = VectorizeDef(node, it.Current(), /*generate_code*/ true); + DCHECK(vectorized_def); + } + // Generate body from the instruction map, but in original program order. + HEnvironment* env = vector_header_->GetFirstInstruction()->GetEnvironment(); + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + auto i = vector_map_->find(it.Current()); + if (i != vector_map_->end() && !i->second->IsInBlock()) { + Insert(vector_body_, i->second); + // Deal with instructions that need an environment, such as the scalar intrinsics. + if (i->second->NeedsEnvironment()) { + i->second->CopyEnvironmentFromWithLoopPhiAdjustment(env, vector_header_); + } } } + vector_index_ = new (global_allocator_) HAdd(induc_type, vector_index_, step); + Insert(vector_body_, vector_index_); } - // Finalize increment and phi. - HInstruction* inc = new (global_allocator_) HAdd(induc_type, vector_phi_, step); - vector_phi_->AddInput(lo); - vector_phi_->AddInput(Insert(vector_body_, inc)); + // Finalize phi for the loop index. + phi->AddInput(lo); + phi->AddInput(vector_index_); + vector_index_ = phi; } // TODO: accept reductions at left-hand-side, mixed-type store idioms, etc. @@ -795,7 +846,7 @@ bool HLoopOptimization::VectorizeDef(LoopNode* node, VectorizeUse(node, value, generate_code, type, restrictions)) { if (generate_code) { GenerateVecSub(index, offset); - GenerateVecMem(instruction, vector_map_->Get(index), vector_map_->Get(value), type); + GenerateVecMem(instruction, vector_map_->Get(index), vector_map_->Get(value), offset, type); } else { vector_refs_->insert(ArrayReference(base, offset, type, /*lhs*/ true)); } @@ -852,7 +903,7 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node, induction_range_.IsUnitStride(instruction, index, &offset)) { if (generate_code) { GenerateVecSub(index, offset); - GenerateVecMem(instruction, vector_map_->Get(index), nullptr, type); + GenerateVecMem(instruction, vector_map_->Get(index), nullptr, offset, type); } else { vector_refs_->insert(ArrayReference(base, offset, type, /*lhs*/ false)); } @@ -1164,7 +1215,7 @@ void HLoopOptimization::GenerateVecInv(HInstruction* org, Primitive::Type type) void HLoopOptimization::GenerateVecSub(HInstruction* org, HInstruction* offset) { if (vector_map_->find(org) == vector_map_->end()) { - HInstruction* subscript = vector_phi_; + HInstruction* subscript = vector_index_; if (offset != nullptr) { subscript = new (global_allocator_) HAdd(Primitive::kPrimInt, subscript, offset); if (org->IsPhi()) { @@ -1178,17 +1229,27 @@ void HLoopOptimization::GenerateVecSub(HInstruction* org, HInstruction* offset) void HLoopOptimization::GenerateVecMem(HInstruction* org, HInstruction* opa, HInstruction* opb, + HInstruction* offset, Primitive::Type type) { HInstruction* vector = nullptr; if (vector_mode_ == kVector) { // Vector store or load. + HInstruction* base = org->InputAt(0); if (opb != nullptr) { vector = new (global_allocator_) HVecStore( - global_allocator_, org->InputAt(0), opa, opb, type, vector_length_); + global_allocator_, base, opa, opb, type, vector_length_); } else { bool is_string_char_at = org->AsArrayGet()->IsStringCharAt(); vector = new (global_allocator_) HVecLoad( - global_allocator_, org->InputAt(0), opa, type, vector_length_, is_string_char_at); + global_allocator_, base, opa, type, vector_length_, is_string_char_at); + } + // Known dynamically enforced alignment? + // TODO: detect offset + constant differences. + // TODO: long run, static alignment analysis? + if (vector_peeling_candidate_ != nullptr && + vector_peeling_candidate_->base == base && + vector_peeling_candidate_->offset == offset) { + vector->AsVecMemoryOperation()->SetAlignment(Alignment(kAlignedBase, 0)); } } else { // Scalar store or load. @@ -1444,10 +1505,57 @@ bool HLoopOptimization::VectorizeHalvingAddIdiom(LoopNode* node, } // +// Vectorization heuristics. +// + +bool HLoopOptimization::IsVectorizationProfitable(int64_t trip_count) { + // Current heuristic: non-empty body with sufficient number + // of iterations (if known). + // TODO: refine by looking at e.g. operation count, alignment, etc. + if (vector_length_ == 0) { + return false; // nothing found + } else if (0 < trip_count && trip_count < vector_length_) { + return false; // insufficient iterations + } + return true; +} + +void HLoopOptimization::SetPeelingCandidate(int64_t trip_count ATTRIBUTE_UNUSED) { + // Current heuristic: none. + // TODO: implement +} + +uint32_t HLoopOptimization::GetUnrollingFactor(HBasicBlock* block, int64_t trip_count) { + // Current heuristic: unroll by 2 on ARM64/X86 for large known trip + // counts and small loop bodies. + // TODO: refine with operation count, remaining iterations, etc. + // Artem had some really cool ideas for this already. + switch (compiler_driver_->GetInstructionSet()) { + case kArm64: + case kX86: + case kX86_64: { + size_t num_instructions = block->GetInstructions().CountSize(); + if (num_instructions <= 10 && trip_count >= 4 * vector_length_) { + return 2; + } + return 1; + } + default: + return 1; + } +} + +// // Helpers. // bool HLoopOptimization::TrySetPhiInduction(HPhi* phi, bool restrict_uses) { + // Special case Phis that have equivalent in a debuggable setup. Our graph checker isn't + // smart enough to follow strongly connected components (and it's probably not worth + // it to make it so). See b/33775412. + if (graph_->IsDebuggable() && phi->HasEquivalentPhi()) { + return false; + } DCHECK(iset_->empty()); ArenaSet<HInstruction*>* set = induction_range_.LookupCycle(phi); if (set != nullptr) { @@ -1576,8 +1684,8 @@ bool HLoopOptimization::TryReplaceWithLastValue(HLoopInformation* loop_info, size_t index = it->GetIndex(); ++it; // increment before replacing if (iset_->find(user->GetHolder()) == iset_->end()) { // not excluded? - HLoopInformation* other_loop_info = user->GetHolder()->GetBlock()->GetLoopInformation(); // Only update environment uses after the loop. + HLoopInformation* other_loop_info = user->GetHolder()->GetBlock()->GetLoopInformation(); if (other_loop_info == nullptr || !other_loop_info->IsIn(*loop_info)) { user->RemoveAsUserOfInput(index); user->SetRawEnvAt(index, replacement); @@ -1614,4 +1722,21 @@ void HLoopOptimization::RemoveDeadInstructions(const HInstructionList& list) { } } +bool HLoopOptimization::CanRemoveCycle() { + for (HInstruction* i : *iset_) { + // We can never remove instructions that have environment + // uses when we compile 'debuggable'. + if (i->HasEnvironmentUses() && graph_->IsDebuggable()) { + return false; + } + // A deoptimization should never have an environment input removed. + for (const HUseListNode<HEnvironment*>& use : i->GetEnvUses()) { + if (use.GetUser()->GetHolder()->IsDeoptimize()) { + return false; + } + } + } + return true; +} + } // namespace art diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h index cc6343aeb5..de4bd85fc8 100644 --- a/compiler/optimizing/loop_optimization.h +++ b/compiler/optimizing/loop_optimization.h @@ -116,14 +116,15 @@ class HLoopOptimization : public HOptimization { void OptimizeInnerLoop(LoopNode* node); // Vectorization analysis and synthesis. - bool CanVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); + bool ShouldVectorize(LoopNode* node, HBasicBlock* block, int64_t trip_count); void Vectorize(LoopNode* node, HBasicBlock* block, HBasicBlock* exit, int64_t trip_count); void GenerateNewLoop(LoopNode* node, HBasicBlock* block, HBasicBlock* new_preheader, HInstruction* lo, HInstruction* hi, - HInstruction* step); + HInstruction* step, + uint32_t unroll); bool VectorizeDef(LoopNode* node, HInstruction* instruction, bool generate_code); bool VectorizeUse(LoopNode* node, HInstruction* instruction, @@ -133,10 +134,11 @@ class HLoopOptimization : public HOptimization { bool TrySetVectorType(Primitive::Type type, /*out*/ uint64_t* restrictions); bool TrySetVectorLength(uint32_t length); void GenerateVecInv(HInstruction* org, Primitive::Type type); - void GenerateVecSub(HInstruction* org, HInstruction* off); + void GenerateVecSub(HInstruction* org, HInstruction* offset); void GenerateVecMem(HInstruction* org, HInstruction* opa, HInstruction* opb, + HInstruction* offset, Primitive::Type type); void GenerateVecOp(HInstruction* org, HInstruction* opa, @@ -151,6 +153,11 @@ class HLoopOptimization : public HOptimization { Primitive::Type type, uint64_t restrictions); + // Vectorization heuristics. + bool IsVectorizationProfitable(int64_t trip_count); + void SetPeelingCandidate(int64_t trip_count); + uint32_t GetUnrollingFactor(HBasicBlock* block, int64_t trip_count); + // Helpers. bool TrySetPhiInduction(HPhi* phi, bool restrict_uses); bool TrySetSimpleLoopHeader(HBasicBlock* block); @@ -208,20 +215,25 @@ class HLoopOptimization : public HOptimization { // Contents reside in phase-local heap memory. ArenaSet<ArrayReference>* vector_refs_; + // Dynamic loop peeling candidate for alignment. + const ArrayReference* vector_peeling_candidate_; + + // Dynamic data dependence test of the form a != b. + HInstruction* vector_runtime_test_a_; + HInstruction* vector_runtime_test_b_; + // Mapping used during vectorization synthesis for both the scalar peeling/cleanup - // loop (simd_ is false) and the actual vector loop (simd_ is true). The data + // loop (mode is kSequential) and the actual vector loop (mode is kVector). The data // structure maps original instructions into the new instructions. // Contents reside in phase-local heap memory. ArenaSafeMap<HInstruction*, HInstruction*>* vector_map_; // Temporary vectorization bookkeeping. + VectorMode vector_mode_; // synthesis mode HBasicBlock* vector_preheader_; // preheader of the new loop HBasicBlock* vector_header_; // header of the new loop HBasicBlock* vector_body_; // body of the new loop - HInstruction* vector_runtime_test_a_; - HInstruction* vector_runtime_test_b_; // defines a != b runtime test - HPhi* vector_phi_; // the Phi representing the normalized loop index - VectorMode vector_mode_; // selects synthesis mode + HInstruction* vector_index_; // normalized index of the new loop friend class LoopOptimizationTest; diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index ffa16dd787..b21c4a5aa7 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -2612,6 +2612,16 @@ class HPhi FINAL : public HVariableInputSizeInstruction { && other->AsPhi()->GetRegNumber() == GetRegNumber(); } + bool HasEquivalentPhi() const { + if (GetPrevious() != nullptr && GetPrevious()->AsPhi()->GetRegNumber() == GetRegNumber()) { + return true; + } + if (GetNext() != nullptr && GetNext()->AsPhi()->GetRegNumber() == GetRegNumber()) { + return true; + } + return false; + } + // Returns the next equivalent phi (starting from the current one) or null if there is none. // An equivalent phi is a phi having the same dex register and type. // It assumes that phis with the same dex register are adjacent. diff --git a/runtime/arch/arm/context_arm.h b/runtime/arch/arm/context_arm.h index 2623ee9315..fa9aa46d4d 100644 --- a/runtime/arch/arm/context_arm.h +++ b/runtime/arch/arm/context_arm.h @@ -25,7 +25,7 @@ namespace art { namespace arm { -class ArmContext : public Context { +class ArmContext FINAL : public Context { public: ArmContext() { Reset(); diff --git a/runtime/arch/arm64/context_arm64.h b/runtime/arch/arm64/context_arm64.h index 105e78461d..36aded07c4 100644 --- a/runtime/arch/arm64/context_arm64.h +++ b/runtime/arch/arm64/context_arm64.h @@ -25,7 +25,7 @@ namespace art { namespace arm64 { -class Arm64Context : public Context { +class Arm64Context FINAL : public Context { public: Arm64Context() { Reset(); diff --git a/runtime/arch/x86/context_x86.h b/runtime/arch/x86/context_x86.h index f482d9ffcb..303dfe361c 100644 --- a/runtime/arch/x86/context_x86.h +++ b/runtime/arch/x86/context_x86.h @@ -25,7 +25,7 @@ namespace art { namespace x86 { -class X86Context : public Context { +class X86Context FINAL : public Context { public: X86Context() { Reset(); diff --git a/runtime/arch/x86_64/context_x86_64.h b/runtime/arch/x86_64/context_x86_64.h index 46f2b63848..f8e2845983 100644 --- a/runtime/arch/x86_64/context_x86_64.h +++ b/runtime/arch/x86_64/context_x86_64.h @@ -25,7 +25,7 @@ namespace art { namespace x86_64 { -class X86_64Context : public Context { +class X86_64Context FINAL : public Context { public: X86_64Context() { Reset(); diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 155498639e..3e7ed9799d 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -61,6 +61,19 @@ DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex, "Wrong runtime-method dex method index"); +ArtMethod* ArtMethod::GetCanonicalMethod(PointerSize pointer_size) { + if (LIKELY(!IsDefault())) { + return this; + } else { + mirror::Class* declaring_class = GetDeclaringClass(); + ArtMethod* ret = declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(), + GetDexMethodIndex(), + pointer_size); + DCHECK(ret != nullptr); + return ret; + } +} + ArtMethod* ArtMethod::GetNonObsoleteMethod() { DCHECK_EQ(kRuntimePointerSize, Runtime::Current()->GetClassLinker()->GetImagePointerSize()); if (LIKELY(!IsObsolete())) { diff --git a/runtime/art_method.h b/runtime/art_method.h index 96306af177..4b3e8efdad 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -483,6 +483,12 @@ class ArtMethod FINAL { } } + // Takes a method and returns a 'canonical' one if the method is default (and therefore + // potentially copied from some other class). For example, this ensures that the debugger does not + // get confused as to which method we are in. + ArtMethod* GetCanonicalMethod(PointerSize pointer_size = kRuntimePointerSize) + REQUIRES_SHARED(Locks::mutator_lock_); + ArtMethod* GetSingleImplementation(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 12bdb32fec..cc12439074 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -77,25 +77,10 @@ static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { return alloc_record_count; } -// Takes a method and returns a 'canonical' one if the method is default (and therefore potentially -// copied from some other class). This ensures that the debugger does not get confused as to which -// method we are in. -static ArtMethod* GetCanonicalMethod(ArtMethod* m) - REQUIRES_SHARED(Locks::mutator_lock_) { - if (LIKELY(!m->IsDefault())) { - return m; - } else { - mirror::Class* declaring_class = m->GetDeclaringClass(); - return declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(), - m->GetDexMethodIndex(), - kRuntimePointerSize); - } -} - class Breakpoint : public ValueObject { public: Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind) - : method_(GetCanonicalMethod(method)), + : method_(method->GetCanonicalMethod(kRuntimePointerSize)), dex_pc_(dex_pc), deoptimization_kind_(deoptimization_kind) { CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing || @@ -125,7 +110,7 @@ class Breakpoint : public ValueObject { // Returns true if the method of this breakpoint and the passed in method should be considered the // same. That is, they are either the same method or they are copied from the same method. bool IsInMethod(ArtMethod* m) const REQUIRES_SHARED(Locks::mutator_lock_) { - return method_ == GetCanonicalMethod(m); + return method_ == m->GetCanonicalMethod(kRuntimePointerSize); } private: @@ -1367,7 +1352,8 @@ JDWP::FieldId Dbg::ToFieldId(const ArtField* f) { static JDWP::MethodId ToMethodId(ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_) { - return static_cast<JDWP::MethodId>(reinterpret_cast<uintptr_t>(GetCanonicalMethod(m))); + return static_cast<JDWP::MethodId>( + reinterpret_cast<uintptr_t>(m->GetCanonicalMethod(kRuntimePointerSize))); } static ArtField* FromFieldId(JDWP::FieldId fid) @@ -2887,7 +2873,7 @@ static void SetEventLocation(JDWP::EventLocation* location, ArtMethod* m, uint32 if (m == nullptr) { memset(location, 0, sizeof(*location)); } else { - location->method = GetCanonicalMethod(m); + location->method = m->GetCanonicalMethod(kRuntimePointerSize); location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint32_t>(-1) : dex_pc; } } diff --git a/runtime/gc/space/region_space-inl.h b/runtime/gc/space/region_space-inl.h index fc24fc2974..82e8f20154 100644 --- a/runtime/gc/space/region_space-inl.h +++ b/runtime/gc/space/region_space-inl.h @@ -48,58 +48,32 @@ inline mirror::Object* RegionSpace::AllocNonvirtual(size_t num_bytes, size_t* by mirror::Object* obj; if (LIKELY(num_bytes <= kRegionSize)) { // Non-large object. - if (!kForEvac) { - obj = current_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } else { - DCHECK(evac_region_ != nullptr); - obj = evac_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } + obj = (kForEvac ? evac_region_ : current_region_)->Alloc(num_bytes, + bytes_allocated, + usable_size, + bytes_tl_bulk_allocated); if (LIKELY(obj != nullptr)) { return obj; } MutexLock mu(Thread::Current(), region_lock_); // Retry with current region since another thread may have updated it. - if (!kForEvac) { - obj = current_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } else { - obj = evac_region_->Alloc(num_bytes, bytes_allocated, usable_size, - bytes_tl_bulk_allocated); - } + obj = (kForEvac ? evac_region_ : current_region_)->Alloc(num_bytes, + bytes_allocated, + usable_size, + bytes_tl_bulk_allocated); if (LIKELY(obj != nullptr)) { return obj; } - if (!kForEvac) { - // Retain sufficient free regions for full evacuation. - if ((num_non_free_regions_ + 1) * 2 > num_regions_) { - return nullptr; - } - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - r->SetNewlyAllocated(); - ++num_non_free_regions_; - obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); - CHECK(obj != nullptr); - current_region_ = r; - return obj; - } - } - } else { - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - ++num_non_free_regions_; - obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); - CHECK(obj != nullptr); - evac_region_ = r; - return obj; - } + Region* r = AllocateRegion(kForEvac); + if (LIKELY(r != nullptr)) { + if (kForEvac) { + evac_region_ = r; + } else { + current_region_ = r; } + obj = r->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated); + CHECK(obj != nullptr); + return obj; } } else { // Large object. diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc index 8d8c4885ef..26b7282a0a 100644 --- a/runtime/gc/space/region_space.cc +++ b/runtime/gc/space/region_space.cc @@ -29,6 +29,9 @@ namespace space { // value of the region size, evaculate the region. static constexpr uint kEvaculateLivePercentThreshold = 75U; +// If we protect the cleared regions. +static constexpr bool kProtectClearedRegions = true; + MemMap* RegionSpace::CreateMemMap(const std::string& name, size_t capacity, uint8_t* requested_begin) { CHECK_ALIGNED(capacity, kRegionSize); @@ -449,21 +452,14 @@ bool RegionSpace::AllocNewTlab(Thread* self, size_t min_bytes) { MutexLock mu(self, region_lock_); RevokeThreadLocalBuffersLocked(self); // Retain sufficient free regions for full evacuation. - if ((num_non_free_regions_ + 1) * 2 > num_regions_) { - return false; - } - for (size_t i = 0; i < num_regions_; ++i) { - Region* r = ®ions_[i]; - if (r->IsFree()) { - r->Unfree(this, time_); - ++num_non_free_regions_; - r->SetNewlyAllocated(); - r->SetTop(r->End()); - r->is_a_tlab_ = true; - r->thread_ = self; - self->SetTlab(r->Begin(), r->Begin() + min_bytes, r->End()); - return true; - } + + Region* r = AllocateRegion(/*for_evac*/ false); + if (r != nullptr) { + r->is_a_tlab_ = true; + r->thread_ = self; + r->SetTop(r->End()); + self->SetTlab(r->Begin(), r->Begin() + min_bytes, r->End()); + return true; } return false; } @@ -543,6 +539,68 @@ size_t RegionSpace::AllocationSizeNonvirtual(mirror::Object* obj, size_t* usable return num_bytes; } +void RegionSpace::Region::Clear(bool zero_and_release_pages) { + top_.StoreRelaxed(begin_); + state_ = RegionState::kRegionStateFree; + type_ = RegionType::kRegionTypeNone; + objects_allocated_.StoreRelaxed(0); + alloc_time_ = 0; + live_bytes_ = static_cast<size_t>(-1); + if (zero_and_release_pages) { + ZeroAndReleasePages(begin_, end_ - begin_); + } + if (kProtectClearedRegions) { + mprotect(begin_, end_ - begin_, PROT_NONE); + } + is_newly_allocated_ = false; + is_a_tlab_ = false; + thread_ = nullptr; +} + +RegionSpace::Region* RegionSpace::AllocateRegion(bool for_evac) { + if (!for_evac && (num_non_free_regions_ + 1) * 2 > num_regions_) { + return nullptr; + } + for (size_t i = 0; i < num_regions_; ++i) { + Region* r = ®ions_[i]; + if (r->IsFree()) { + r->Unfree(this, time_); + ++num_non_free_regions_; + if (!for_evac) { + // Evac doesn't count as newly allocated. + r->SetNewlyAllocated(); + } + return r; + } + } + return nullptr; +} + +void RegionSpace::Region::MarkAsAllocated(RegionSpace* region_space, uint32_t alloc_time) { + DCHECK(IsFree()); + alloc_time_ = alloc_time; + region_space->AdjustNonFreeRegionLimit(idx_); + type_ = RegionType::kRegionTypeToSpace; + if (kProtectClearedRegions) { + mprotect(Begin(), kRegionSize, PROT_READ | PROT_WRITE); + } +} + +void RegionSpace::Region::Unfree(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateAllocated; +} + +void RegionSpace::Region::UnfreeLarge(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateLarge; +} + +void RegionSpace::Region::UnfreeLargeTail(RegionSpace* region_space, uint32_t alloc_time) { + MarkAsAllocated(region_space, alloc_time); + state_ = RegionState::kRegionStateLargeTail; +} + } // namespace space } // namespace gc } // namespace art diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h index 323ccdbd74..8907b07bf2 100644 --- a/runtime/gc/space/region_space.h +++ b/runtime/gc/space/region_space.h @@ -284,20 +284,7 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { return type_; } - void Clear(bool zero_and_release_pages) { - top_.StoreRelaxed(begin_); - state_ = RegionState::kRegionStateFree; - type_ = RegionType::kRegionTypeNone; - objects_allocated_.StoreRelaxed(0); - alloc_time_ = 0; - live_bytes_ = static_cast<size_t>(-1); - if (zero_and_release_pages) { - ZeroAndReleasePages(begin_, end_ - begin_); - } - is_newly_allocated_ = false; - is_a_tlab_ = false; - thread_ = nullptr; - } + void Clear(bool zero_and_release_pages); ALWAYS_INLINE mirror::Object* Alloc(size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, @@ -315,31 +302,16 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { // Given a free region, declare it non-free (allocated). void Unfree(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateAllocated; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); void UnfreeLarge(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateLarge; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); void UnfreeLargeTail(RegionSpace* region_space, uint32_t alloc_time) - REQUIRES(region_space->region_lock_) { - DCHECK(IsFree()); - state_ = RegionState::kRegionStateLargeTail; - type_ = RegionType::kRegionTypeToSpace; - alloc_time_ = alloc_time; - region_space->AdjustNonFreeRegionLimit(idx_); - } + REQUIRES(region_space->region_lock_); + + void MarkAsAllocated(RegionSpace* region_space, uint32_t alloc_time) + REQUIRES(region_space->region_lock_); void SetNewlyAllocated() { is_newly_allocated_ = true; @@ -539,6 +511,8 @@ class RegionSpace FINAL : public ContinuousMemMapAllocSpace { } } + Region* AllocateRegion(bool for_evac) REQUIRES(region_lock_); + Mutex region_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; uint32_t time_; // The time as the number of collections since the startup. diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 45788e7617..de8c44e304 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -17,6 +17,7 @@ #include "interpreter_switch_impl.h" #include "base/enums.h" +#include "base/memory_tool.h" #include "experimental_flags.h" #include "interpreter_common.h" #include "jit/jit.h" @@ -64,13 +65,22 @@ namespace interpreter { } // Code to run before each dex instruction. -#define PREAMBLE() \ - do { \ - if (UNLIKELY(instrumentation->HasDexPcListeners())) { \ - instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), \ - shadow_frame.GetMethod(), dex_pc); \ +#define PREAMBLE_SAVE(save_ref) \ + { \ + if (UNLIKELY(instrumentation->HasDexPcListeners()) && \ + UNLIKELY(!DoDexPcMoveEvent(self, \ + code_item, \ + shadow_frame, \ + dex_pc, \ + instrumentation, \ + save_ref))) { \ + HANDLE_PENDING_EXCEPTION(); \ + break; \ } \ - } while (false) + } \ + do {} while (false) + +#define PREAMBLE() PREAMBLE_SAVE(nullptr) #define BRANCH_INSTRUMENTATION(offset) \ do { \ @@ -104,6 +114,46 @@ namespace interpreter { } \ } while (false) +// Unlike most other events the DexPcMovedEvent can be sent when there is a pending exception (if +// the next instruction is MOVE_EXCEPTION). This means it needs to be handled carefully to be able +// to detect exceptions thrown by the DexPcMovedEvent itself. These exceptions could be thrown by +// jvmti-agents while handling breakpoint or single step events. We had to move this into its own +// function because it was making ExecuteSwitchImpl have too large a stack. +#ifdef ADDRESS_SANITIZER +NO_INLINE +#endif // ADDRESS_SANITIZER +static bool DoDexPcMoveEvent(Thread* self, + const DexFile::CodeItem* code_item, + const ShadowFrame& shadow_frame, + uint32_t dex_pc, + const instrumentation::Instrumentation* instrumentation, + JValue* save_ref) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(instrumentation->HasDexPcListeners()); + StackHandleScope<2> hs(self); + Handle<mirror::Throwable> thr(hs.NewHandle(self->GetException())); + mirror::Object* null_obj = nullptr; + HandleWrapper<mirror::Object> h( + hs.NewHandleWrapper(LIKELY(save_ref == nullptr) ? &null_obj : save_ref->GetGCRoot())); + self->ClearException(); + instrumentation->DexPcMovedEvent(self, + shadow_frame.GetThisObject(code_item->ins_size_), + shadow_frame.GetMethod(), + dex_pc); + if (UNLIKELY(self->IsExceptionPending())) { + // We got a new exception in the dex-pc-moved event. We just let this exception replace the old + // one. + // TODO It would be good to add the old exception to the suppressed exceptions of the new one if + // possible. + return false; + } else { + if (UNLIKELY(!thr.IsNull())) { + self->SetException(thr.Get()); + } + return true; + } +} + template<bool do_access_check, bool transaction_active> JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowFrame& shadow_frame, JValue result_register, @@ -198,7 +248,7 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, inst = inst->Next_1xx(); break; case Instruction::MOVE_RESULT_OBJECT: - PREAMBLE(); + PREAMBLE_SAVE(&result_register); shadow_frame.SetVRegReference(inst->VRegA_11x(inst_data), result_register.GetL()); inst = inst->Next_1xx(); break; diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index e38f265c5a..619a49aa71 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -27,6 +27,7 @@ cc_defaults { "fixed_up_dex_file.cc", "object_tagging.cc", "OpenjdkJvmTi.cc", + "ti_breakpoint.cc", "ti_class.cc", "ti_class_definition.cc", "ti_class_loader.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 0896210f1c..e3768b358f 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -48,6 +48,7 @@ #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_breakpoint.h" #include "ti_class.h" #include "ti_dump.h" #include "ti_field.h" @@ -619,20 +620,17 @@ class JvmtiFunctions { return ERR(NOT_IMPLEMENTED); } - static jvmtiError SetBreakpoint(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jlocation location ATTRIBUTE_UNUSED) { + + static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_breakpoint_events); - return ERR(NOT_IMPLEMENTED); + return BreakpointUtil::SetBreakpoint(env, method, location); } - static jvmtiError ClearBreakpoint(jvmtiEnv* env, - jmethodID method ATTRIBUTE_UNUSED, - jlocation location ATTRIBUTE_UNUSED) { + static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_generate_breakpoint_events); - return ERR(NOT_IMPLEMENTED); + return BreakpointUtil::ClearBreakpoint(env, method, location); } static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index b5f12191e6..2d5d527e68 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -34,6 +34,7 @@ #include <memory> #include <type_traits> +#include <unordered_map> #include <unordered_set> #include <jni.h> @@ -46,10 +47,12 @@ #include "java_vm_ext.h" #include "jni_env_ext.h" #include "jvmti.h" +#include "ti_breakpoint.h" namespace art { class ArtField; -} +class ArtMethod; +} // namespace art namespace openjdkjvmti { @@ -76,6 +79,9 @@ struct ArtJvmTiEnv : public jvmtiEnv { std::unordered_set<art::ArtField*> access_watched_fields; std::unordered_set<art::ArtField*> modify_watched_fields; + // Set of breakpoints is unique to each jvmtiEnv. + std::unordered_set<Breakpoint> breakpoints; + ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler); static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) { @@ -223,10 +229,10 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_source_debug_extension = 1, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, - .can_generate_single_step_events = 0, + .can_generate_single_step_events = 1, .can_generate_exception_events = 0, .can_generate_frame_pop_events = 0, - .can_generate_breakpoint_events = 0, + .can_generate_breakpoint_events = 1, .can_suspend = 0, .can_redefine_any_class = 0, .can_get_current_thread_cpu_time = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index af99233f90..f30d7cecb3 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -22,6 +22,7 @@ #include "events.h" #include "jni_internal.h" #include "ScopedLocalRef.h" +#include "ti_breakpoint.h" #include "art_jvmti.h" @@ -217,6 +218,32 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A } } +// Need to give custom specializations for Breakpoint since it needs to filter out which particular +// methods/dex_pcs agents get notified on. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID jmethod, + jlocation location) const { + art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); + for (ArtJvmTiEnv* env : envs) { + // Search for a breakpoint on this particular method and location. + if (env != nullptr && + ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) && + env->breakpoints.find({method, location}) != env->breakpoints.end()) { + // We temporarily clear any pending exceptions so the event can call back into java code. + ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); + jnienv->ExceptionClear(); + auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env); + (*callback)(env, jnienv, jni_thread, jmethod, location); + if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { + jnienv->Throw(thr.get()); + } + } + } +} + // Need to give custom specializations for FieldAccess and FieldModification since they need to // filter out which particular fields agents want to get notified on. // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 989b9af591..f749daa918 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -423,14 +423,30 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat } } - // Call-back for when the dex pc moves in a method. We don't currently have any events associated - // with this. - void DexPcMoved(art::Thread* self ATTRIBUTE_UNUSED, + // Call-back for when the dex pc moves in a method. + void DexPcMoved(art::Thread* self, art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, - art::ArtMethod* method ATTRIBUTE_UNUSED, - uint32_t new_dex_pc ATTRIBUTE_UNUSED) + art::ArtMethod* method, + uint32_t new_dex_pc) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - return; + DCHECK(!method->IsRuntimeMethod()); + // Default methods might be copied to multiple classes. We need to get the canonical version of + // this method so that we can check for breakpoints correctly. + // TODO We should maybe do this on other events to ensure that we are consistent WRT default + // methods. This could interact with obsolete methods if we ever let interface redefinition + // happen though. + method = method->GetCanonicalMethod(); + art::JNIEnvExt* jnienv = self->GetJniEnv(); + jmethodID jmethod = art::jni::EncodeArtMethod(method); + jlocation location = static_cast<jlocation>(new_dex_pc); + // Step event is reported first according to the spec. + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) { + RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location); + } + // Next we do the Breakpoint events. The Dispatch code will filter the individual + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) { + RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location); + } } // Call-back for when we read from a field. @@ -563,6 +579,9 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { return art::instrumentation::Instrumentation::kFieldWritten; case ArtJvmtiEvent::kFieldAccess: return art::instrumentation::Instrumentation::kFieldRead; + case ArtJvmtiEvent::kBreakpoint: + case ArtJvmtiEvent::kSingleStep: + return art::instrumentation::Instrumentation::kDexPcMoved; default: LOG(FATAL) << "Unknown event "; return 0; @@ -580,6 +599,8 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, art::gc::kCollectorTypeInstrumentation); art::ScopedSuspendAll ssa("jvmti method tracing installation"); if (enable) { + // TODO Depending on the features being used we should be able to avoid deoptimizing everything + // like we do here. if (!instr->AreAllMethodsDeoptimized()) { instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true); } @@ -601,6 +622,17 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { SetupGcPauseTracking(gc_pause_listener_.get(), event, enable); return; + case ArtJvmtiEvent::kBreakpoint: + case ArtJvmtiEvent::kSingleStep: { + ArtJvmtiEvent other = (event == ArtJvmtiEvent::kBreakpoint) ? ArtJvmtiEvent::kSingleStep + : ArtJvmtiEvent::kBreakpoint; + // We only need to do anything if there isn't already a listener installed/held-on by the + // other jvmti event that uses DexPcMoved. + if (!IsEventEnabledAnywhere(other)) { + SetupTraceListener(method_trace_listener_.get(), event, enable); + } + return; + } case ArtJvmtiEvent::kMethodEntry: case ArtJvmtiEvent::kMethodExit: case ArtJvmtiEvent::kFieldAccess: diff --git a/runtime/openjdkjvmti/ti_breakpoint.cc b/runtime/openjdkjvmti/ti_breakpoint.cc new file mode 100644 index 0000000000..6d0e2c60c1 --- /dev/null +++ b/runtime/openjdkjvmti/ti_breakpoint.cc @@ -0,0 +1,114 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <functional> + +#include "ti_breakpoint.h" + +#include "art_jvmti.h" +#include "art_method-inl.h" +#include "base/enums.h" +#include "dex_file_annotations.h" +#include "events-inl.h" +#include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/object_array-inl.h" +#include "modifiers.h" +#include "runtime_callbacks.h" +#include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" +#include "thread-current-inl.h" +#include "thread_list.h" +#include "ti_phase.h" + +namespace openjdkjvmti { + +size_t Breakpoint::hash() const { + return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_)) + ^ std::hash<jlocation> {}(location_); +} + +Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_(loc) { + DCHECK(!m->IsDefault() || !m->IsCopied() || !m->IsInvokable()) + << "Flags are: 0x" << std::hex << m->GetAccessFlags(); +} + +void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) { + std::vector<Breakpoint> to_remove; + for (const Breakpoint& b : env->breakpoints) { + if (b.GetMethod()->GetDeclaringClass() == klass) { + to_remove.push_back(b); + } + } + for (const Breakpoint& b : to_remove) { + auto it = env->breakpoints.find(b); + DCHECK(it != env->breakpoints.end()); + env->breakpoints.erase(it); + } +} + +jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + // Need to get mutator_lock_ so we can find the interface version of any default methods. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod(); + if (location < 0 || static_cast<uint32_t>(location) >= + art_method->GetCodeItem()->insns_size_in_code_units_) { + return ERR(INVALID_LOCATION); + } + auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location}); + if (!res_pair.second) { + // Didn't get inserted because it's already present! + return ERR(DUPLICATE); + } + return OK; +} + +jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) { + ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (method == nullptr) { + return ERR(INVALID_METHODID); + } + // Need to get mutator_lock_ so we can find the interface version of any default methods. + art::ScopedObjectAccess soa(art::Thread::Current()); + auto pos = env->breakpoints.find( + /* Breakpoint */ {art::jni::DecodeArtMethod(method)->GetCanonicalMethod(), location}); + if (pos == env->breakpoints.end()) { + return ERR(NOT_FOUND); + } + env->breakpoints.erase(pos); + return OK; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_breakpoint.h b/runtime/openjdkjvmti/ti_breakpoint.h new file mode 100644 index 0000000000..c3dbef7baf --- /dev/null +++ b/runtime/openjdkjvmti/ti_breakpoint.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ + +#include "jni.h" +#include "jvmti.h" + +#include "base/mutex.h" + +namespace art { +class ArtMethod; +namespace mirror { +class Class; +} // namespace mirror +} // namespace art + +namespace openjdkjvmti { + +struct ArtJvmTiEnv; + +class Breakpoint { + public: + Breakpoint(art::ArtMethod* m, jlocation loc); + + // Get the hash code of this breakpoint. + size_t hash() const; + + bool operator==(const Breakpoint& other) const { + return method_ == other.method_ && location_ == other.location_; + } + + art::ArtMethod* GetMethod() const { + return method_; + } + + jlocation GetLocation() const { + return location_; + } + + private: + art::ArtMethod* method_; + jlocation location_; +}; + +class BreakpointUtil { + public: + static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location); + static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location); + // Used by class redefinition to remove breakpoints on redefined classes. + static void RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) + REQUIRES(art::Locks::mutator_lock_); +}; + +} // namespace openjdkjvmti + +namespace std { +template<> struct hash<openjdkjvmti::Breakpoint> { + size_t operator()(const openjdkjvmti::Breakpoint& b) const { + return b.hash(); + } +}; + +} // namespace std +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_ diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 5422f48664..debee913ee 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -64,6 +64,7 @@ #include "object_lock.h" #include "runtime.h" #include "ScopedLocalRef.h" +#include "ti_breakpoint.h" #include "ti_class_loader.h" #include "transform.h" #include "verifier/method_verifier.h" @@ -380,7 +381,7 @@ jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, art::jit::ScopedJitSuspend suspend_jit; // Get shared mutator lock so we can lock all the classes. art::ScopedObjectAccess soa(self); - Redefiner r(runtime, self, error_msg); + Redefiner r(env, runtime, self, error_msg); for (const ArtClassDefinition& def : definitions) { // Only try to transform classes that have been modified. if (def.IsModified()) { @@ -1200,6 +1201,10 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( return true; } +void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() { + BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass()); +} + void Redefiner::ClassRedefinition::UnregisterBreakpoints() { DCHECK(art::Dbg::IsDebuggerActive()); art::JDWP::JdwpState* state = art::Dbg::GetJdwpState(); @@ -1342,6 +1347,7 @@ jvmtiError Redefiner::Run() { // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile()); + redef.UnregisterJvmtiBreakpoints(); } RestoreObsoleteMethodMapsIfUnneeded(holder); // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index ec4a8b2789..27d7c3d726 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -199,6 +199,8 @@ class Redefiner { void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_); + // This should be done with all threads suspended. + void UnregisterJvmtiBreakpoints() REQUIRES(art::Locks::mutator_lock_); private: Redefiner* driver_; @@ -208,6 +210,7 @@ class Redefiner { art::ArrayRef<const unsigned char> original_dex_file_; }; + ArtJvmTiEnv* env_; jvmtiError result_; art::Runtime* runtime_; art::Thread* self_; @@ -216,10 +219,12 @@ class Redefiner { // mirror::Class difficult and confusing. std::string* error_msg_; - Redefiner(art::Runtime* runtime, + Redefiner(ArtJvmTiEnv* env, + art::Runtime* runtime, art::Thread* self, std::string* error_msg) - : result_(ERR(INTERNAL)), + : env_(env), + result_(ERR(INTERNAL)), runtime_(runtime), self_(self), redefinitions_(), diff --git a/test/593-checker-shift-and-simplifier/expected.txt b/test/593-checker-shift-and-simplifier/expected.txt index b0aad4deb5..f8d85db6ac 100644 --- a/test/593-checker-shift-and-simplifier/expected.txt +++ b/test/593-checker-shift-and-simplifier/expected.txt @@ -1 +1,2 @@ passed +passed diff --git a/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali b/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali new file mode 100644 index 0000000000..6b0d68306b --- /dev/null +++ b/test/593-checker-shift-and-simplifier/smali/SmaliTests.smali @@ -0,0 +1,58 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# A very particular set of operations that caused a double removal by the +# ARM64 simplifier doing "forward" removals (b/27851582). + +## CHECK-START-ARM: int SmaliTests.operations() instruction_simplifier_arm (before) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] +## CHECK-DAG: And [<<Not>>,<<Shl>>] + +## CHECK-START-ARM: int SmaliTests.operations() instruction_simplifier_arm (after) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 + +## CHECK-START-ARM64: int SmaliTests.operations() instruction_simplifier_arm64 (before) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] +## CHECK-DAG: And [<<Not>>,<<Shl>>] + +## CHECK-START-ARM64: int SmaliTests.operations() instruction_simplifier_arm64 (after) +## CHECK-DAG: <<Get:i\d+>> ArrayGet +## CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] +## CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 +.method public static operations()I + .registers 6 + .prologue + + # int r = a[0]; + sget-object v4, LMain;->a:[I + const/4 v5, 0x0 + aget v2, v4, v5 + # int n = ~r; + not-int v1, v2 + # int s = r << 2; + shl-int/lit8 v3, v2, 0x2 + # int a = s & n; + and-int v0, v3, v1 + # return a + return v0 +.end method diff --git a/test/593-checker-shift-and-simplifier/src/Main.java b/test/593-checker-shift-and-simplifier/src/Main.java index c9826bc546..f0ef0e6949 100644 --- a/test/593-checker-shift-and-simplifier/src/Main.java +++ b/test/593-checker-shift-and-simplifier/src/Main.java @@ -14,30 +14,20 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { - private static int[] a = { 10 }; + static int[] a = { 10 }; // A very particular set of operations that caused a double removal by the // ARM64 simplifier doing "forward" removals (b/27851582). - /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (before) - /// CHECK-DAG: <<Get:i\d+>> ArrayGet - /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] - /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] - /// CHECK-DAG: And [<<Not>>,<<Shl>>] - // /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (after) /// CHECK-DAG: <<Get:i\d+>> ArrayGet /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] /// CHECK-DAG: DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2 - /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (before) - /// CHECK-DAG: <<Get:i\d+>> ArrayGet - /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] - /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}] - /// CHECK-DAG: And [<<Not>>,<<Shl>>] - // /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (after) /// CHECK-DAG: <<Get:i\d+>> ArrayGet /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>] @@ -56,5 +46,21 @@ public class Main { } else { System.out.println("passed"); } + + if ($noinline$runSmaliTest("operations") != 32) { + System.out.println("failed"); + } else { + System.out.println("passed"); + } + } + + public static int $noinline$runSmaliTest(String name) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name); + return (Integer) m.invoke(null); + } catch (Exception ex) { + throw new Error(ex); + } } } diff --git a/test/633-checker-rtp-getclass/build b/test/633-checker-rtp-getclass/build new file mode 100755 index 0000000000..49292c9ac1 --- /dev/null +++ b/test/633-checker-rtp-getclass/build @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 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. + +# This checker test is incompatible with jack bytecode output, +# so force it to use javac/dx. +export USE_JACK=false +# Also disable desugar because it is missing in jack platform builds. +export DESUGAR=false + +./default-build "$@" diff --git a/test/633-checker-rtp-getclass/smali/SmaliTests.smali b/test/633-checker-rtp-getclass/smali/SmaliTests.smali new file mode 100644 index 0000000000..9ea04498d7 --- /dev/null +++ b/test/633-checker-rtp-getclass/smali/SmaliTests.smali @@ -0,0 +1,65 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.class public LSmaliTests; +.super Ljava/lang/Object; + +# Checker test to make sure the only inlined instruction is SubMain.bar. + +## CHECK-START: int SmaliTests.$opt$noinline$foo(Main) inliner (after) +## CHECK-DAG: InvokeVirtual method_name:Main.foo +## CHECK-DAG: <<Const:i\d+>> IntConstant 3 +## CHECK: begin_block +## CHECK: BoundType klass:SubMain +## CHECK: Return [<<Const>>] +## CHECK-NOT: begin_block +## CHECK: end_block +.method public static $opt$noinline$foo(LMain;)I + .registers 3 + .param p0, "o" # LMain; + .prologue + + # if (doThrow) { throw new Error(); } + sget-boolean v0, LMain;->doThrow:Z + if-eqz v0, :doThrow_false + new-instance v0, Ljava/lang/Error; + invoke-direct {v0}, Ljava/lang/Error;-><init>()V + throw v0 + + :doThrow_false + # if (o.getClass() == Main.class || o.getClass() != SubMain.class) + invoke-virtual {p0}, LMain;->getClass()Ljava/lang/Class; + move-result-object v0 + const-class v1, LMain; + if-eq v0, v1, :class_is_Main_and_not_SubMain + + invoke-virtual {p0}, LMain;->getClass()Ljava/lang/Class; + move-result-object v0 + const-class v1, LSubMain; + if-eq v0, v1, :else + + :class_is_Main_and_not_SubMain + # return o.foo() + invoke-virtual {p0}, LMain;->foo()I + move-result v0 + return v0 + + :else + # return o.bar() + invoke-virtual {p0}, LMain;->bar()I + move-result v0 + return v0 +.end method + + diff --git a/test/633-checker-rtp-getclass/src/Main.java b/test/633-checker-rtp-getclass/src/Main.java index f29c139f63..d1145af2a5 100644 --- a/test/633-checker-rtp-getclass/src/Main.java +++ b/test/633-checker-rtp-getclass/src/Main.java @@ -14,34 +14,13 @@ * limitations under the License. */ +import java.lang.reflect.Method; + public class Main { public static void main(String[] args) { - System.out.println($opt$noinline$foo(new Main())); - System.out.println($opt$noinline$foo(new SubMain())); - System.out.println($opt$noinline$foo(new SubSubMain())); - } - - - // Checker test to make sure the only inlined instruction is - // SubMain.bar. - /// CHECK-START: int Main.$opt$noinline$foo(Main) inliner (after) - /// CHECK-DAG: InvokeVirtual method_name:Main.foo - /// CHECK-DAG: <<Const:i\d+>> IntConstant 3 - /// CHECK: begin_block - /// CHECK: BoundType klass:SubMain - /// CHECK: Return [<<Const>>] - /// CHECK-NOT: begin_block - /// CHECK: end_block - public static int $opt$noinline$foo(Main o) { - if (doThrow) { throw new Error(); } - // To exercise the bug on Jack, we need two getClass compares. - if (o.getClass() == Main.class || o.getClass() != SubMain.class) { - return o.foo(); - } else { - // We used to wrongly bound the type of o to `Main` here and then realize that's - // impossible and mark this branch as dead. - return o.bar(); - } + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new Main())); + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new SubMain())); + System.out.println($noinline$runSmaliTest("$opt$noinline$foo", new SubSubMain())); } public int bar() { @@ -53,6 +32,16 @@ public class Main { } public static boolean doThrow = false; + + public static int $noinline$runSmaliTest(String name, Main input) { + try { + Class<?> c = Class.forName("SmaliTests"); + Method m = c.getMethod(name, Main.class); + return (Integer) m.invoke(null, input); + } catch (Exception ex) { + throw new Error(ex); + } + } } class SubMain extends Main { diff --git a/test/656-checker-simd-opt/expected.txt b/test/656-checker-simd-opt/expected.txt new file mode 100644 index 0000000000..b0aad4deb5 --- /dev/null +++ b/test/656-checker-simd-opt/expected.txt @@ -0,0 +1 @@ +passed diff --git a/test/656-checker-simd-opt/info.txt b/test/656-checker-simd-opt/info.txt new file mode 100644 index 0000000000..185d2b1b95 --- /dev/null +++ b/test/656-checker-simd-opt/info.txt @@ -0,0 +1 @@ +Tests around optimizations of SIMD code. diff --git a/test/656-checker-simd-opt/src/Main.java b/test/656-checker-simd-opt/src/Main.java new file mode 100644 index 0000000000..0d0885c85a --- /dev/null +++ b/test/656-checker-simd-opt/src/Main.java @@ -0,0 +1,69 @@ +/* + * 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. + */ + +/** + * Tests for SIMD related optimizations. + */ +public class Main { + + /// CHECK-START: void Main.unroll(float[], float[]) loop_optimization (before) + /// CHECK-DAG: <<Cons:f\d+>> FloatConstant 2.5 loop:none + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get:f\d+>> ArrayGet loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul:f\d+>> Mul [<<Get>>,<<Cons>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Mul>>] loop:<<Loop>> outer_loop:none + // + /// CHECK-START-ARM64: void Main.unroll(float[], float[]) loop_optimization (after) + /// CHECK-DAG: <<Cons:f\d+>> FloatConstant 2.5 loop:none + /// CHECK-DAG: <<Incr:i\d+>> IntConstant 4 loop:none + /// CHECK-DAG: <<Repl:d\d+>> VecReplicateScalar [<<Cons>>] loop:none + /// CHECK-NOT: VecReplicateScalar + /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none + /// CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul1:d\d+>> VecMul [<<Get1>>,<<Repl>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Phi>>,<<Mul1>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Add:i\d+>> Add [<<Phi>>,<<Incr>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: <<Mul2:d\d+>> VecMul [<<Get2>>,<<Repl>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: VecStore [{{l\d+}},<<Add>>,<<Mul2>>] loop:<<Loop>> outer_loop:none + /// CHECK-DAG: Add [<<Add>>,<<Incr>>] loop:<<Loop>> outer_loop:none + private static void unroll(float[] x, float[] y) { + for (int i = 0; i < 100; i++) { + x[i] = y[i] * 2.5f; + } + } + + public static void main(String[] args) { + float[] x = new float[100]; + float[] y = new float[100]; + for (int i = 0; i < 100; i++) { + x[i] = 0.0f; + y[i] = 2.0f; + } + unroll(x, y); + for (int i = 0; i < 100; i++) { + expectEquals(5.0f, x[i]); + expectEquals(2.0f, y[i]); + } + System.out.println("passed"); + } + + private static void expectEquals(float expected, float result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } +} diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index 30ad532f6c..8e42a48865 100644 --- a/test/988-method-trace/expected.txt +++ b/test/988-method-trace/expected.txt @@ -1,4 +1,4 @@ -.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> <= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> => art.Test988$IterOp() .=> public java.lang.Object() @@ -143,11 +143,11 @@ fibonacci(5)=5 ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:209) - at art.Test988$IterOp.applyAsInt(Test988.java:204) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:267) - at Main.main(Main.java:19) + art.Test988.iter_fibonacci(Test988.java:228) + art.Test988$IterOp.applyAsInt(Test988.java:223) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:286) + <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> ...<= public java.lang.Error(java.lang.String) -> <null: null> @@ -163,11 +163,11 @@ fibonacci(5)=5 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.iter_fibonacci(Test988.java:209) - at art.Test988$IterOp.applyAsInt(Test988.java:204) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:267) - at Main.main(Main.java:19) + art.Test988.iter_fibonacci(Test988.java:228) + art.Test988$IterOp.applyAsInt(Test988.java:223) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:286) + <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> @@ -244,11 +244,11 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>> .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:231) - at art.Test988$RecurOp.applyAsInt(Test988.java:226) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:268) - at Main.main(Main.java:19) + art.Test988.fibonacci(Test988.java:250) + art.Test988$RecurOp.applyAsInt(Test988.java:245) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:287) + <additional hidden frames> > ....<= public java.lang.Throwable(java.lang.String) -> <null: null> ...<= public java.lang.Error(java.lang.String) -> <null: null> @@ -264,14 +264,14 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null> ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null> fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 - at art.Test988.fibonacci(Test988.java:231) - at art.Test988$RecurOp.applyAsInt(Test988.java:226) - at art.Test988.doFibTest(Test988.java:297) - at art.Test988.run(Test988.java:268) - at Main.main(Main.java:19) + art.Test988.fibonacci(Test988.java:250) + art.Test988$RecurOp.applyAsInt(Test988.java:245) + art.Test988.doFibTest(Test988.java:316) + art.Test988.run(Test988.java:287) + <additional hidden frames> .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null> => public static native java.lang.Thread java.lang.Thread.currentThread() -<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>> +<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <<non-deterministic>: <non-deterministic>> => public static native void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index 6a45c0eaa2..e40c612851 100644 --- a/test/988-method-trace/src/art/Test988.java +++ b/test/988-method-trace/src/art/Test988.java @@ -31,6 +31,7 @@ public class Test988 { // Methods with non-deterministic output that should not be printed. static Set<Method> NON_DETERMINISTIC_OUTPUT_METHODS = new HashSet<>(); + static Set<Method> NON_DETERMINISTIC_OUTPUT_TYPE_METHODS = new HashSet<>(); static { try { @@ -39,6 +40,7 @@ public class Test988 { } catch (Exception e) {} try { NON_DETERMINISTIC_OUTPUT_METHODS.add(Thread.class.getDeclaredMethod("currentThread")); + NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.add(Thread.class.getDeclaredMethod("currentThread")); } catch (Exception e) {} } @@ -66,7 +68,16 @@ public class Test988 { return arrayToString(val); } else if (val instanceof Throwable) { StringWriter w = new StringWriter(); - ((Throwable) val).printStackTrace(new PrintWriter(w)); + Throwable thr = ((Throwable) val); + w.write(thr.getClass().getName() + ": " + thr.getMessage() + "\n"); + for (StackTraceElement e : thr.getStackTrace()) { + if (e.getClassName().startsWith("art.")) { + w.write("\t" + e + "\n"); + } else { + w.write("\t<additional hidden frames>\n"); + break; + } + } return w.toString(); } else { return val.toString(); @@ -134,8 +145,16 @@ public class Test988 { if (val != null) { klass = val.getClass(); } + String klass_print; + if (klass == null) { + klass_print = "null"; + } else if (NON_DETERMINISTIC_OUTPUT_TYPE_METHODS.contains(m)) { + klass_print = "<non-deterministic>"; + } else { + klass_print = klass.toString(); + } System.out.println( - whitespace(cnt) + "<= " + m + " -> <" + klass + ": " + print + ">"); + whitespace(cnt) + "<= " + m + " -> <" + klass_print + ": " + print + ">"); } } diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/988-method-trace/src/art/Trace.java +++ b/test/988-method-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/989-method-trace-throw/src/art/Trace.java +++ b/test/989-method-trace-throw/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/990-field-trace/src/art/Trace.java b/test/990-field-trace/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/990-field-trace/src/art/Trace.java +++ b/test/990-field-trace/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/991-field-trace-2/src/art/Trace.java b/test/991-field-trace-2/src/art/Trace.java index 9c27c9f69e..ba3d397b0b 100644 --- a/test/991-field-trace-2/src/art/Trace.java +++ b/test/991-field-trace-2/src/art/Trace.java @@ -25,6 +25,7 @@ public class Trace { Method exitMethod, Method fieldAccess, Method fieldModify, + Method singleStep, Thread thr); public static native void disableTracing(Thread thr); @@ -32,14 +33,20 @@ public class Trace { Method fieldAccess, Method fieldModify, Thread thr) { - enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); } public static void enableMethodTracing(Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr) { - enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); } public static native void watchFieldAccess(Field f); diff --git a/test/992-source-data/expected.txt b/test/992-source-data/expected.txt index 480d8a4fe7..4db8df0ada 100644 --- a/test/992-source-data/expected.txt +++ b/test/992-source-data/expected.txt @@ -1,6 +1,6 @@ class art.Test992 is defined in file "Test992.java" class art.Test992$Target1 is defined in file "Test992.java" -class art.Test2 is defined in file "Test2.java" +class art.Target2 is defined in file "Target2.java" int does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION class java.lang.Integer is defined in file "Integer.java" class java.lang.Object is defined in file "Object.java" diff --git a/test/992-source-data/src/art/Test2.java b/test/992-source-data/src/art/Target2.java index dbb1089c5e..7c29d88871 100644 --- a/test/992-source-data/src/art/Test2.java +++ b/test/992-source-data/src/art/Target2.java @@ -16,4 +16,4 @@ package art; -public class Test2 {} +public class Target2 {} diff --git a/test/992-source-data/src/art/Test992.java b/test/992-source-data/src/art/Test992.java index db6ea73856..d9ab112726 100644 --- a/test/992-source-data/src/art/Test992.java +++ b/test/992-source-data/src/art/Test992.java @@ -25,7 +25,7 @@ public class Test992 { public static void run() { doTest(Test992.class); doTest(Target1.class); - doTest(Test2.class); + doTest(Target2.class); doTest(Integer.TYPE); doTest(Integer.class); doTest(Object.class); diff --git a/test/993-breakpoints/breakpoints.cc b/test/993-breakpoints/breakpoints.cc new file mode 100644 index 0000000000..129207098d --- /dev/null +++ b/test/993-breakpoints/breakpoints.cc @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 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 <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test993Breakpoints { + +extern "C" JNIEXPORT +jobject JNICALL Java_art_Test993_constructNative(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return nullptr; + } + return env->NewObject(clazz, method); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test993_invokeNative(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject target, + jclass clazz, + jobject thizz) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + if (thizz == nullptr) { + env->CallStaticVoidMethod(clazz, method); + } else { + env->CallVoidMethod(thizz, method); + } +} + +} // namespace Test993Breakpoints +} // namespace art + diff --git a/test/993-breakpoints/expected.txt b/test/993-breakpoints/expected.txt new file mode 100644 index 0000000000..962154734b --- /dev/null +++ b/test/993-breakpoints/expected.txt @@ -0,0 +1,613 @@ +Running static invoke + Breaking on [] + Native invoking: public static void art.Test993.breakpoint() args: [this: null] + Reflective invoking: public static void art.Test993.breakpoint() args: [this: null] + Invoking "Test993::breakpoint" + Breaking on [public static void art.Test993.breakpoint() @ 41] + Native invoking: public static void art.Test993.breakpoint() args: [this: null] + Breakpoint: public static void art.Test993.breakpoint() @ line=41 + Reflective invoking: public static void art.Test993.breakpoint() args: [this: null] + Breakpoint: public static void art.Test993.breakpoint() @ line=41 + Invoking "Test993::breakpoint" + Breakpoint: public static void art.Test993.breakpoint() @ line=41 +Running private static invoke + Breaking on [] + Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null] + Invoking "Test993::privateBreakpoint" + Breaking on [private static void art.Test993.privateBreakpoint() @ 45] + Native invoking: private static void art.Test993.privateBreakpoint() args: [this: null] + Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45 + Invoking "Test993::privateBreakpoint" + Breakpoint: private static void art.Test993.privateBreakpoint() @ line=45 +Running interface static invoke + Breaking on [] + Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Invoking "Breakable::iBreakpoint" + Breaking on [public static void art.Test993$Breakable.iBreakpoint() @ 51] + Native invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 + Reflective invoking: public static void art.Test993$Breakable.iBreakpoint() args: [this: null] + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 + Invoking "Breakable::iBreakpoint" + Breakpoint: public static void art.Test993$Breakable.iBreakpoint() @ line=51 +Running TestClass1 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Invoking "((Breakable)new TestClass1()).breakit()" + Invoking "new TestClass1().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1().breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass1ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Invoking "((Breakable)new TestClass1ext()).breakit()" + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Invoking "new TestClass1ext().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass1ext.breakit() @ 74] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass1ext.breakit() @ 74] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass1ext.breakit() args: [this: TestClass1Ext] + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass1)new TestClass1ext()).breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass1ext().breakit()" + Breakpoint: public void art.Test993$TestClass1ext.breakit() @ line=74 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass2 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Invoking "((Breakable)new TestClass2()).breakit()" + Invoking "new TestClass2().breakit()" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Invoking "((Breakable)new TestClass2()).breakit()" + Invoking "new TestClass2().breakit()" + Breaking on [public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2().breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2().breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 +Running TestClass2ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Invoking "((Breakable)new TestClass2ext()).breakit()" + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Invoking "new TestClass2ext().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Invoking "((Breakable)new TestClass2ext()).breakit()" + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Invoking "new TestClass2ext().breakit())" + Breaking on [public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breaking on [public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass2.breakit() @ 83, public void art.Test993$TestClass2ext.breakit() @ 91] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Native invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Reflective invoking: public void art.Test993$TestClass2ext.breakit() args: [this: TestClass2ext] + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((Breakable)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "((TestClass2)new TestClass2ext()).breakit()" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 + Invoking "new TestClass2ext().breakit())" + Breakpoint: public void art.Test993$TestClass2ext.breakit() @ line=91 + Breakpoint: public void art.Test993$TestClass2.breakit() @ line=83 +Running TestClass3 invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Invoking "((Breakable)new TestClass3()).breakit()" + Invoking "new TestClass3().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3().breakit())" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running TestClass3ext invokes + Breaking on [] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Invoking "((Breakable)new TestClass3ext()).breakit()" + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Invoking "new TestClass3ext().breakit())" + Breaking on [public default void art.Test993$Breakable.breakit() @ 55] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Breaking on [public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breaking on [public default void art.Test993$Breakable.breakit() @ 55, public void art.Test993$TestClass3.breakit() @ 99, public void art.Test993$TestClass3ext.breakit() @ 108] + Native invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Native invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public default void art.Test993$Breakable.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Reflective invoking: public void art.Test993$TestClass3ext.breakit() args: [this: TestClass3ext] + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((Breakable)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "((TestClass3)new TestClass3ext()).breakit()" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 + Invoking "new TestClass3ext().breakit())" + Breakpoint: public void art.Test993$TestClass3ext.breakit() @ line=108 + Breakpoint: public void art.Test993$TestClass3.breakit() @ line=99 + Breakpoint: public default void art.Test993$Breakable.breakit() @ line=55 +Running private instance invoke + Breaking on [] + Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4] + Invoking "new TestClass4().callPrivateMethod()" + Breaking on [private void art.Test993$TestClass4.privateMethod() @ 118] + Native invoking: private void art.Test993$TestClass4.privateMethod() args: [this: TestClass4] + Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 + Invoking "new TestClass4().callPrivateMethod()" + Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118 +Running TestClass1 constructor + Breaking on [] + Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1 + Created: TestClass1 + Reflective constructor: public art.Test993$TestClass1() + Created: TestClass1 + Constructing: new TestClass1() + Created: TestClass1 + Breaking on [public art.Test993$TestClass1() @ 62] + Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 + Reflective constructor: public art.Test993$TestClass1() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 + Constructing: new TestClass1() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1 +Running TestClass1ext constructor + Breaking on [] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Created: TestClass1Ext + Constructing: new TestClass1ext() + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1() @ 62] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1ext() @ 70] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Created: TestClass1Ext + Breaking on [public art.Test993$TestClass1() @ 62, public art.Test993$TestClass1ext() @ 70] + Native constructor: public art.Test993$TestClass1ext(), type: class art.Test993$TestClass1ext + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Reflective constructor: public art.Test993$TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext + Constructing: new TestClass1ext() + Breakpoint: public art.Test993$TestClass1ext() @ line=70 + Breakpoint: public art.Test993$TestClass1() @ line=62 + Created: TestClass1Ext diff --git a/test/993-breakpoints/info.txt b/test/993-breakpoints/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/993-breakpoints/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/993-breakpoints/run b/test/993-breakpoints/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/993-breakpoints/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/993-breakpoints/src/Main.java b/test/993-breakpoints/src/Main.java new file mode 100644 index 0000000000..b11f6f8174 --- /dev/null +++ b/test/993-breakpoints/src/Main.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 Main { + public static void main(String[] args) throws Exception { + art.Test993.run(); + } +} diff --git a/test/993-breakpoints/src/art/Breakpoint.java b/test/993-breakpoints/src/art/Breakpoint.java new file mode 100644 index 0000000000..2a370ebd40 --- /dev/null +++ b/test/993-breakpoints/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/993-breakpoints/src/art/Test993.java b/test/993-breakpoints/src/art/Test993.java new file mode 100644 index 0000000000..781ebffc0f --- /dev/null +++ b/test/993-breakpoints/src/art/Test993.java @@ -0,0 +1,498 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.function.IntUnaryOperator; +import java.util.function.Supplier; + +public class Test993 { + + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + + // A function we can use as a start breakpoint. + public static void breakpoint() { + return; + } + + private static void privateBreakpoint() { + return; + } + + // An interface with a default method we can break on. + static interface Breakable { + public static void iBreakpoint() { + return; + } + + public default void breakit() { + return; + } + } + + // A class that has a default method we breakpoint on. + public static class TestClass1 implements Breakable { + public TestClass1() { + super(); + } + public String toString() { return "TestClass1"; } + } + + // A class that overrides a default method that we can breakpoint on and calls super. + public static class TestClass1ext extends TestClass1 { + public TestClass1ext() { + super(); + } + public String toString() { return "TestClass1Ext"; } + public void breakit() { + super.breakit(); + } + } + + + // A class that overrides a default method that we can breakpoint on. + public static class TestClass2 implements Breakable { + public String toString() { return "TestClass2"; } + public void breakit() { + return; + } + } + + // A class that overrides a default method that we can breakpoint on and calls super. + public static class TestClass2ext extends TestClass2 { + public String toString() { return "TestClass2ext"; } + public void breakit() { + super.breakit(); + } + } + + // A class that overrides a default method and calls it directly with interface invoke-super + public static class TestClass3 implements Breakable { + public String toString() { return "TestClass3"; } + public void breakit() { + Breakable.super.breakit(); + } + } + + // A class that overrides a default method that we can breakpoint on and calls super to a class + // that uses interface-invoke-super. + public static class TestClass3ext extends TestClass3 { + public String toString() { return "TestClass3ext"; } + public void breakit() { + super.breakit(); + } + } + + public static class TestClass4 { + public String toString() { return "TestClass4"; } + public void callPrivateMethod() { + privateMethod(); + } + private void privateMethod() { + return; + } + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + } + + public static interface ThrowRunnable extends Runnable { + public default void run() { + try { + runThrow(); + } catch (Exception e) { + throw new Error("Caught error while running " + this, e); + } + } + public void runThrow() throws Exception; + } + + public static class InvokeDirect implements Runnable { + String msg; + Runnable r; + public InvokeDirect(String msg, Runnable r) { + this.msg = msg; + this.r = r; + } + @Override + public void run() { + System.out.println("\t\tInvoking \"" + msg + "\""); + r.run(); + } + } + + public static class InvokeReflect implements ThrowRunnable { + Method m; + Object this_arg; + public InvokeReflect(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void runThrow() throws Exception { + System.out.println("\t\tReflective invoking: " + m + " args: [this: " + this_arg + "]"); + m.invoke(this_arg); + } + } + + public static class InvokeNative implements Runnable { + Method m; + Object this_arg; + public InvokeNative(Method m, Object this_arg) { + this.m = m; + this.this_arg = this_arg; + } + + @Override + public void run() { + System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]"); + invokeNative(m, m.getDeclaringClass(), this_arg); + } + } + + public static native void invokeNative(Method m, Class<?> clazz, Object thizz); + + public static class ConstructDirect implements Runnable { + String msg; + Supplier<Object> s; + public ConstructDirect(String msg, Supplier<Object> s) { + this.msg = msg; + this.s = s; + } + + @Override + public void run() { + System.out.println("\t\tConstructing: " + msg); + System.out.println("\t\t\tCreated: " + s.get()); + } + } + + public static class ConstructReflect implements ThrowRunnable { + Constructor<?> m; + public ConstructReflect(Constructor<?> m) { + this.m = m; + } + + @Override + public void runThrow() throws Exception { + System.out.println("\t\tReflective constructor: " + m); + System.out.println("\t\t\tCreated: " + m.newInstance()); + } + } + + public static class ConstructNative implements Runnable { + Constructor<?> m; + Class type; + public ConstructNative(Constructor<?> m) { + this.m = m; + this.type = m.getDeclaringClass(); + } + + @Override + public void run() { + System.out.println("\t\tNative constructor: " + m + ", type: " + type); + System.out.println("\t\t\tCreated: " + constructNative(m, type)); + } + } + + public static native Object constructNative(Constructor m, Class<?> clazz); + + private static <T> List<List<T>> combinations(List<T> items, int len) { + if (len > items.size()) { + throw new Error("Bad length" + len + " " + items); + } + if (len == 1) { + List<List<T>> out = new ArrayList<>(); + for (T t : items) { + out.add(Arrays.asList(t)); + } + return out; + } + List<List<T>> out = new ArrayList<>(); + for (int rem = 0; rem <= items.size() - len; rem++) { + for (List<T> others : combinations(items.subList(rem + 1, items.size()), len - 1)) { + List<T> newone = new ArrayList<>(); + newone.add(items.get(rem)); + newone.addAll(others); + out.add(newone); + } + } + return out; + } + + private static <T> List<List<T>> allCombinations(List<T> items) { + List<List<T>> out = new ArrayList<List<T>>(); + out.add(new ArrayList<>()); + for (int i = 0; i < items.size(); i++) { + out.addAll(combinations(items, i + 1)); + } + return out; + } + + private static Breakpoint.Manager.BP BP(Executable m) { + return new Breakpoint.Manager.BP(m); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test993.class, + Test993.class.getDeclaredMethod("notifyBreakpointReached", + Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + runMethodTests(); + runConstructorTests(); + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } + + public static void runConstructorTests() throws Exception { + // The constructors we will be breaking on. + Constructor<?> tc1_construct = TestClass1.class.getConstructor(); + Constructor<?> tc1ext_construct = TestClass1ext.class.getConstructor(); + + Runnable[] tc1_constructors = new Runnable[] { + new ConstructNative(tc1_construct), + new ConstructReflect(tc1_construct), + new ConstructDirect("new TestClass1()", TestClass1::new), + }; + Breakpoint.Manager.BP[] tc1_bps = new Breakpoint.Manager.BP[] { + BP(tc1_construct), + }; + runTestGroups("TestClass1 constructor", tc1_constructors, tc1_bps); + + Runnable[] tc1ext_constructors = new Runnable[] { + new ConstructNative(tc1ext_construct), + new ConstructReflect(tc1ext_construct), + new ConstructDirect("new TestClass1ext()", TestClass1ext::new), + }; + Breakpoint.Manager.BP[] tc1ext_bps = new Breakpoint.Manager.BP[] { + BP(tc1_construct), BP(tc1ext_construct), + }; + runTestGroups("TestClass1ext constructor", tc1ext_constructors, tc1ext_bps); + } + + public static void runMethodTests() throws Exception { + // The methods we will be breaking on. + Method breakpoint_method = Test993.class.getDeclaredMethod("breakpoint"); + Method private_breakpoint_method = Test993.class.getDeclaredMethod("privateBreakpoint"); + Method i_breakpoint_method = Breakable.class.getDeclaredMethod("iBreakpoint"); + Method breakit_method = Breakable.class.getDeclaredMethod("breakit"); + Method breakit_method_tc1ext = TestClass1ext.class.getDeclaredMethod("breakit"); + Method breakit_method_tc2 = TestClass2.class.getDeclaredMethod("breakit"); + Method breakit_method_tc2ext = TestClass2ext.class.getDeclaredMethod("breakit"); + Method breakit_method_tc3 = TestClass3.class.getDeclaredMethod("breakit"); + Method breakit_method_tc3ext = TestClass3ext.class.getDeclaredMethod("breakit"); + Method private_method = TestClass4.class.getDeclaredMethod("privateMethod"); + + // Static class function + Runnable[] static_invokes = new Runnable[] { + new InvokeNative(breakpoint_method, null), + + new InvokeReflect(breakpoint_method, null), + + new InvokeDirect("Test993::breakpoint", Test993::breakpoint), + }; + Breakpoint.Manager.BP[] static_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakpoint_method) + }; + runTestGroups("static invoke", static_invokes, static_breakpoints); + + // Static private class function + Runnable[] private_static_invokes = new Runnable[] { + new InvokeNative(private_breakpoint_method, null), + + new InvokeDirect("Test993::privateBreakpoint", Test993::privateBreakpoint), + }; + Breakpoint.Manager.BP[] private_static_breakpoints = new Breakpoint.Manager.BP[] { + BP(private_breakpoint_method) + }; + runTestGroups("private static invoke", private_static_invokes, private_static_breakpoints); + + // Static interface function. + Runnable[] i_static_invokes = new Runnable[] { + new InvokeNative(i_breakpoint_method, null), + + new InvokeReflect(i_breakpoint_method, null), + + new InvokeDirect("Breakable::iBreakpoint", Breakable::iBreakpoint), + }; + Breakpoint.Manager.BP[] i_static_breakpoints = new Breakpoint.Manager.BP[] { + BP(i_breakpoint_method) + }; + runTestGroups("interface static invoke", i_static_invokes, i_static_breakpoints); + + // Call default method through a class. + Runnable[] tc1_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass1()), + + new InvokeReflect(breakit_method, new TestClass1()), + + new InvokeDirect("((Breakable)new TestClass1()).breakit()", + () -> ((Breakable)new TestClass1()).breakit()), + new InvokeDirect("new TestClass1().breakit()", + () -> new TestClass1().breakit()), + }; + Breakpoint.Manager.BP[] tc1_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method) + }; + runTestGroups("TestClass1 invokes", tc1_invokes, tc1_breakpoints); + + // Call default method through an override and normal invoke-super + Runnable[] tc1ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass1ext()), + new InvokeNative(breakit_method_tc1ext, new TestClass1ext()), + + new InvokeReflect(breakit_method, new TestClass1ext()), + new InvokeReflect(breakit_method_tc1ext, new TestClass1ext()), + + new InvokeDirect("((Breakable)new TestClass1ext()).breakit()", + () -> ((Breakable)new TestClass1ext()).breakit()), + new InvokeDirect("((TestClass1)new TestClass1ext()).breakit()", + () -> ((TestClass1)new TestClass1ext()).breakit()), + new InvokeDirect("new TestClass1ext().breakit()", + () -> new TestClass1ext().breakit()), + }; + Breakpoint.Manager.BP[] tc1ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc1ext) + }; + runTestGroups("TestClass1ext invokes", tc1ext_invokes, tc1ext_breakpoints); + + // Override default/interface method. + Runnable[] tc2_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass2()), + new InvokeNative(breakit_method_tc2, new TestClass2()), + + new InvokeReflect(breakit_method, new TestClass2()), + new InvokeReflect(breakit_method_tc2, new TestClass2()), + + new InvokeDirect("((Breakable)new TestClass2()).breakit()", + () -> ((Breakable)new TestClass2()).breakit()), + new InvokeDirect("new TestClass2().breakit()", + () -> new TestClass2().breakit()), + }; + Breakpoint.Manager.BP[] tc2_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc2) + }; + runTestGroups("TestClass2 invokes", tc2_invokes, tc2_breakpoints); + + // Call overridden method using invoke-super + Runnable[] tc2ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass2ext()), + new InvokeNative(breakit_method_tc2, new TestClass2ext()), + new InvokeNative(breakit_method_tc2ext, new TestClass2ext()), + + new InvokeReflect(breakit_method, new TestClass2ext()), + new InvokeReflect(breakit_method_tc2, new TestClass2ext()), + new InvokeReflect(breakit_method_tc2ext, new TestClass2ext()), + + new InvokeDirect("((Breakable)new TestClass2ext()).breakit()", + () -> ((Breakable)new TestClass2ext()).breakit()), + new InvokeDirect("((TestClass2)new TestClass2ext()).breakit()", + () -> ((TestClass2)new TestClass2ext()).breakit()), + new InvokeDirect("new TestClass2ext().breakit())", + () -> new TestClass2ext().breakit()), + }; + Breakpoint.Manager.BP[] tc2ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc2), BP(breakit_method_tc2ext) + }; + runTestGroups("TestClass2ext invokes", tc2ext_invokes, tc2ext_breakpoints); + + // Override default method and call it using interface-invoke-super + Runnable[] tc3_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass3()), + new InvokeNative(breakit_method_tc3, new TestClass3()), + + new InvokeReflect(breakit_method, new TestClass3()), + new InvokeReflect(breakit_method_tc3, new TestClass3()), + + new InvokeDirect("((Breakable)new TestClass3()).breakit()", + () -> ((Breakable)new TestClass3()).breakit()), + new InvokeDirect("new TestClass3().breakit())", + () -> new TestClass3().breakit()), + }; + Breakpoint.Manager.BP[] tc3_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc3) + }; + runTestGroups("TestClass3 invokes", tc3_invokes, tc3_breakpoints); + + // Call overridden method using invoke-super + Runnable[] tc3ext_invokes = new Runnable[] { + new InvokeNative(breakit_method, new TestClass3ext()), + new InvokeNative(breakit_method_tc3, new TestClass3ext()), + new InvokeNative(breakit_method_tc3ext, new TestClass3ext()), + + new InvokeReflect(breakit_method, new TestClass3ext()), + new InvokeReflect(breakit_method_tc3, new TestClass3ext()), + new InvokeReflect(breakit_method_tc3ext, new TestClass3ext()), + + new InvokeDirect("((Breakable)new TestClass3ext()).breakit()", + () -> ((Breakable)new TestClass3ext()).breakit()), + new InvokeDirect("((TestClass3)new TestClass3ext()).breakit()", + () -> ((TestClass3)new TestClass3ext()).breakit()), + new InvokeDirect("new TestClass3ext().breakit())", + () -> new TestClass3ext().breakit()), + }; + Breakpoint.Manager.BP[] tc3ext_breakpoints = new Breakpoint.Manager.BP[] { + BP(breakit_method), BP(breakit_method_tc3), BP(breakit_method_tc3ext) + }; + runTestGroups("TestClass3ext invokes", tc3ext_invokes, tc3ext_breakpoints); + + // private instance method. + Runnable[] private_instance_invokes = new Runnable[] { + new InvokeNative(private_method, new TestClass4()), + + new InvokeDirect("new TestClass4().callPrivateMethod()", + () -> new TestClass4().callPrivateMethod()), + }; + Breakpoint.Manager.BP[] private_instance_breakpoints = new Breakpoint.Manager.BP[] { + BP(private_method) + }; + runTestGroups( + "private instance invoke", private_instance_invokes, private_instance_breakpoints); + } + + private static void runTestGroups(String name, + Runnable[] invokes, + Breakpoint.Manager.BP[] breakpoints) throws Exception { + System.out.println("Running " + name); + for (List<Breakpoint.Manager.BP> bps : allCombinations(Arrays.asList(breakpoints))) { + System.out.println("\tBreaking on " + bps); + for (Runnable test : invokes) { + MANAGER.clearAllBreakpoints(); + MANAGER.setBreakpoints(bps.toArray(new Breakpoint.Manager.BP[0])); + test.run(); + } + } + } +} diff --git a/test/994-breakpoint-line/expected.txt b/test/994-breakpoint-line/expected.txt new file mode 100644 index 0000000000..5899659b3c --- /dev/null +++ b/test/994-breakpoint-line/expected.txt @@ -0,0 +1,34 @@ +Breaking on line: 29 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29 + argument was true +Breaking on line: 29 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29 + argument was false +Breaking on line: 30 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30 + argument was true +Breaking on line: 30 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30 + argument was false +Breaking on line: 31 calling with arg: true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=31 + argument was true +Breaking on line: 31 calling with arg: false + argument was false +Breaking on line: 33 calling with arg: true + argument was true +Breaking on line: 33 calling with arg: false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=33 + argument was false +Breaking on line: 35 calling with arg: true + argument was true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35 +Breaking on line: 35 calling with arg: false + argument was false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35 +Breaking on line: 36 calling with arg: true + argument was true + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36 +Breaking on line: 36 calling with arg: false + argument was false + Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36 diff --git a/test/994-breakpoint-line/info.txt b/test/994-breakpoint-line/info.txt new file mode 100644 index 0000000000..210dea0471 --- /dev/null +++ b/test/994-breakpoint-line/info.txt @@ -0,0 +1,5 @@ +Test basic JVMTI breakpoint functionality. + +This test ensures we can place breakpoints on particular lines of a method. It +sets breakpoints on each line in turn of a function with multiple execution +paths and then runs the function, receiving the breakpoint events. diff --git a/test/994-breakpoint-line/run b/test/994-breakpoint-line/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/994-breakpoint-line/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/994-breakpoint-line/src/Main.java b/test/994-breakpoint-line/src/Main.java new file mode 100644 index 0000000000..39cfeb3ee4 --- /dev/null +++ b/test/994-breakpoint-line/src/Main.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 Main { + public static void main(String[] args) throws Exception { + art.Test994.run(); + } +} diff --git a/test/994-breakpoint-line/src/art/Breakpoint.java b/test/994-breakpoint-line/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/994-breakpoint-line/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/994-breakpoint-line/src/art/Test994.java b/test/994-breakpoint-line/src/art/Test994.java new file mode 100644 index 0000000000..6a1c354b39 --- /dev/null +++ b/test/994-breakpoint-line/src/art/Test994.java @@ -0,0 +1,72 @@ +/* + * 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. + */ + +package art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test994 { + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + public static void doNothing() {} + + // Method with multiple paths we can break on. + public static void doMultiPath(boolean bit) { + doNothing(); + if (bit) { + System.out.println("\targument was true"); + } else { + System.out.println("\targument was false"); + } + doNothing(); + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println( + "\tBreakpoint reached: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test994.class, + Test994.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Method multipath_method = Test994.class.getDeclaredMethod("doMultiPath", Boolean.TYPE); + + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(multipath_method); + + // Make sure everything is in the same order. + Arrays.sort(lines); + + boolean[] values = new boolean[] { true, false }; + + for (Breakpoint.LineNumber line : lines) { + MANAGER.clearAllBreakpoints(); + MANAGER.setBreakpoint(multipath_method, line.location); + for (boolean arg : values) { + System.out.println("Breaking on line: " + line.line + " calling with arg: " + arg); + doMultiPath(arg); + } + } + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } +} diff --git a/test/995-breakpoints-throw/expected.txt b/test/995-breakpoints-throw/expected.txt new file mode 100644 index 0000000000..a565b7cf14 --- /dev/null +++ b/test/995-breakpoints-throw/expected.txt @@ -0,0 +1,34 @@ +Test "call Test995::breakpoint": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "call Test995::breakpoint": No error caught with handler "do nothing" +Test "call Test995::breakpoint": Finished running with handler "do nothing" +Test "call Test995::breakpointCatch": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpointCatch() @ line=48 +Test "call Test995::breakpointCatch": No error caught with handler "do nothing" +Test "call Test995::breakpointCatch": Finished running with handler "do nothing" +Test "call Test995::breakpointCatchLate": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38 +Test "call Test995::breakpointCatchLate": No error caught with handler "do nothing" +Test "call Test995::breakpointCatchLate": Finished running with handler "do nothing" +Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "do nothing" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "catch subroutine Test995::breakpoint": No error caught with handler "do nothing" +Test "catch subroutine Test995::breakpoint": Finished running with handler "do nothing" +Test "call Test995::breakpoint": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Test "call Test995::breakpoint": Caught error java.lang.Error:"throwing error!" with handler "throw" +Test "call Test995::breakpoint": Finished running with handler "throw" +Test "call Test995::breakpointCatch": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpointCatch() @ line=48 +Caught java.lang.Error: "throwing error!" +Test "call Test995::breakpointCatch": No error caught with handler "throw" +Test "call Test995::breakpointCatch": Finished running with handler "throw" +Test "call Test995::breakpointCatchLate": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpointCatchLate() @ line=38 +Test "call Test995::breakpointCatchLate": Caught error java.lang.Error:"throwing error!" with handler "throw" +Test "call Test995::breakpointCatchLate": Finished running with handler "throw" +Test "catch subroutine Test995::breakpoint": Running breakpoint with handler "throw" + Breakpoint: public static void art.Test995.breakpoint() @ line=34 +Caught java.lang.Error:"throwing error!" +Test "catch subroutine Test995::breakpoint": No error caught with handler "throw" +Test "catch subroutine Test995::breakpoint": Finished running with handler "throw" diff --git a/test/995-breakpoints-throw/info.txt b/test/995-breakpoints-throw/info.txt new file mode 100644 index 0000000000..80f9cf94bf --- /dev/null +++ b/test/995-breakpoints-throw/info.txt @@ -0,0 +1,6 @@ +Test basic JVMTI breakpoint functionality. + +Tests that it is possible to throw exceptions while handling breakpoint events +and that they are handled appropriately. This includes checking that it is +possible for the method being breakpointed to catch exceptions thrown by the +handler. diff --git a/test/995-breakpoints-throw/run b/test/995-breakpoints-throw/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/995-breakpoints-throw/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/995-breakpoints-throw/src/Main.java b/test/995-breakpoints-throw/src/Main.java new file mode 100644 index 0000000000..6f80b43255 --- /dev/null +++ b/test/995-breakpoints-throw/src/Main.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 Main { + public static void main(String[] args) throws Exception { + art.Test995.run(); + } +} diff --git a/test/995-breakpoints-throw/src/art/Breakpoint.java b/test/995-breakpoints-throw/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/995-breakpoints-throw/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/995-breakpoints-throw/src/art/Test995.java b/test/995-breakpoints-throw/src/art/Test995.java new file mode 100644 index 0000000000..a4023fb80a --- /dev/null +++ b/test/995-breakpoints-throw/src/art/Test995.java @@ -0,0 +1,136 @@ +/* + * 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. + */ + +package art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test995 { + public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager(); + public static BreakpointHandler HANDLER = null; + + public static void doNothing() { } + + public static interface BreakpointHandler { + public void breakpointReached(Executable e, long loc); + } + + public static void breakpoint() { + return; + } + + public static void breakpointCatchLate() { + doNothing(); + try { + doNothing(); + } catch (Throwable t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + } + } + + public static void breakpointCatch() { + try { + doNothing(); + } catch (Throwable t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + } + } + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + System.out.println("\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc)); + HANDLER.breakpointReached(e, loc); + } + + + public static BreakpointHandler makeHandler(String name, BreakpointHandler h) { + return new BreakpointHandler() { + public String toString() { + return name; + } + public void breakpointReached(Executable e, long loc) { + h.breakpointReached(e, loc); + } + }; + } + + public static Runnable makeTest(String name, Runnable test) { + return new Runnable() { + public String toString() { return name; } + public void run() { test.run(); } + }; + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test995.class, + Test995.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Method breakpoint_method = Test995.class.getDeclaredMethod("breakpoint"); + Method breakpoint_catch_method = Test995.class.getDeclaredMethod("breakpointCatch"); + Method breakpoint_catch_late_method = Test995.class.getDeclaredMethod("breakpointCatchLate"); + MANAGER.setBreakpoint(breakpoint_method, Breakpoint.getStartLocation(breakpoint_method)); + MANAGER.setBreakpoint( + breakpoint_catch_method, Breakpoint.getStartLocation(breakpoint_catch_method)); + MANAGER.setBreakpoint( + breakpoint_catch_late_method, Breakpoint.getStartLocation(breakpoint_catch_late_method)); + + BreakpointHandler[] handlers = new BreakpointHandler[] { + makeHandler("do nothing", (e, l) -> {}), + makeHandler("throw", (e, l) -> { throw new Error("throwing error!"); }), + }; + + Runnable[] tests = new Runnable[] { + makeTest("call Test995::breakpoint", Test995::breakpoint), + makeTest("call Test995::breakpointCatch", Test995::breakpointCatch), + makeTest("call Test995::breakpointCatchLate", Test995::breakpointCatchLate), + makeTest("catch subroutine Test995::breakpoint", + () -> { + try { + breakpoint(); + } catch (Throwable t) { + System.out.printf("Caught %s:\"%s\"\n", t.getClass().getName(), t.getMessage()); + } + }), + }; + + for (BreakpointHandler handler : handlers) { + for (Runnable test : tests) { + try { + HANDLER = handler; + System.out.printf("Test \"%s\": Running breakpoint with handler \"%s\"\n", + test, handler); + test.run(); + System.out.printf("Test \"%s\": No error caught with handler \"%s\"\n", + test, handler); + } catch (Throwable e) { + System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n", + test, e.getClass().getName(), e.getMessage(), handler); + } + System.out.printf("Test \"%s\": Finished running with handler \"%s\"\n", test, handler); + HANDLER = null; + } + } + + MANAGER.clearAllBreakpoints(); + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } +} diff --git a/test/996-breakpoint-obsolete/expected.txt b/test/996-breakpoint-obsolete/expected.txt new file mode 100644 index 0000000000..e0d419e3f5 --- /dev/null +++ b/test/996-breakpoint-obsolete/expected.txt @@ -0,0 +1,14 @@ +Initially setting breakpoint to line 42 +Running transform without redefinition. +Should be after first breakpoint. +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=42 +Running transform with redefinition. +Redefining calling function! +Setting breakpoint on now obsolete method to line 40 +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=40 +Should be after first breakpoint. +Running transform post redefinition. Should not hit any breakpoints. +Doing nothing transformed +Setting initial breakpoint on redefined method. +Doing nothing transformed +Breakpoint reached: public void art.Test996$Transform.run(java.lang.Runnable) @ line=8 diff --git a/test/996-breakpoint-obsolete/info.txt b/test/996-breakpoint-obsolete/info.txt new file mode 100644 index 0000000000..58536acece --- /dev/null +++ b/test/996-breakpoint-obsolete/info.txt @@ -0,0 +1,4 @@ +Test JVMTI breakpoint/obsolete method interaction. + +This checks that redefining a class will clear breakpoints on the class's +methods and that it is possible to set breakpoints on obsolete methods. diff --git a/test/996-breakpoint-obsolete/obsolete_breakpoints.cc b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc new file mode 100644 index 0000000000..b6a67e4a08 --- /dev/null +++ b/test/996-breakpoint-obsolete/obsolete_breakpoints.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 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 <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test996ObsoleteBreakpoints { + +static constexpr jint kNumFrames = 10; + +static jmethodID GetFirstObsoleteMethod(JNIEnv* env, jvmtiEnv* jvmti_env) { + jint frame_count; + jvmtiFrameInfo frames[kNumFrames]; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetStackTrace(nullptr, // current thread + 0, + kNumFrames, + frames, + &frame_count))) { + return nullptr; + } + for (jint i = 0; i < frame_count; i++) { + jboolean is_obsolete = false; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->IsMethodObsolete(frames[i].method, &is_obsolete))) { + return nullptr; + } + if (is_obsolete) { + return frames[i].method; + } + } + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Unable to find obsolete method!"); + return nullptr; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test996_setBreakpointOnObsoleteMethod( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jlong loc) { + jmethodID method = GetFirstObsoleteMethod(env, jvmti_env); + if (method == nullptr) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, loc)); +} + +} // namespace Test996ObsoleteBreakpoints +} // namespace art diff --git a/test/996-breakpoint-obsolete/run b/test/996-breakpoint-obsolete/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/996-breakpoint-obsolete/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/996-breakpoint-obsolete/src/Main.java b/test/996-breakpoint-obsolete/src/Main.java new file mode 100644 index 0000000000..1b9b0a9b4b --- /dev/null +++ b/test/996-breakpoint-obsolete/src/Main.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 Main { + public static void main(String[] args) throws Exception { + art.Test996.run(); + } +} diff --git a/test/996-breakpoint-obsolete/src/art/Breakpoint.java b/test/996-breakpoint-obsolete/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/996-breakpoint-obsolete/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/996-breakpoint-obsolete/src/art/Redefinition.java b/test/996-breakpoint-obsolete/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/996-breakpoint-obsolete/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/996-breakpoint-obsolete/src/art/Test996.java b/test/996-breakpoint-obsolete/src/art/Test996.java new file mode 100644 index 0000000000..f3166c33c7 --- /dev/null +++ b/test/996-breakpoint-obsolete/src/art/Test996.java @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Base64; + +public class Test996 { + // The line we are going to break on. This should be the println in the Transform class. We set a + // breakpoint here after we have redefined the class. + public static final int TRANSFORM_BREAKPOINT_REDEFINED_LINE = 40; + + // The line we initially set a breakpoint on. This should be the doNothing call. This should be + // cleared by the redefinition and should only be caught on the initial run. + public static final int TRANSFORM_BREAKPOINT_INITIAL_LINE = 42; + + // A function that doesn't do anything. Used for giving places to break on in a function. + public static void doNothing() {} + + public static final class Transform { + public void run(Runnable r) { + r.run(); + // Make sure we don't change anything above this line to keep all the breakpoint stuff + // working. We will be putting a breakpoint before this line in the runnable. + System.out.println("Should be after first breakpoint."); + // This is set as a breakpoint prior to redefinition. It should not be hit. + doNothing(); + } + } + + /* ******************************************************************************************** */ + // Try to keep all edits to this file below the above line. If edits need to be made above this + // line be sure to update the TRANSFORM_BREAKPOINT_REDEFINED_LINE and + // TRANSFORM_BREAKPOINT_INITIAL_LINE to their appropriate values. + + public static final int TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE = 8; + + // The base64 encoding of the following class. The redefined 'run' method should have the same + // instructions as the original. This means that the locations of each line should stay the same + // and the set of valid locations will not change. We use this to ensure that breakpoints are + // removed from the redefined method. + // public static final class Transform { + // public void run(Runnable r) { + // r.run(); + // System.out.println("Doing nothing transformed"); + // doNothing(); // try to catch non-removed breakpoints + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAKAoACAARCwASABMJABQAFQgAFgoAFwAYCgAZABoHABsHAB4BAAY8aW5pdD4BAAMo" + + "KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQADcnVuAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAApTb3VyY2VGaWxlAQAMVGVzdDk5Ni5qYXZhDAAJAAoHAB8MAA0ACgcAIAwAIQAiAQAZRG9p" + + "bmcgbm90aGluZyB0cmFuc2Zvcm1lZAcAIwwAJAAlBwAmDAAnAAoBABVhcnQvVGVzdDk5NiRUcmFu" + + "c2Zvcm0BAAlUcmFuc2Zvcm0BAAxJbm5lckNsYXNzZXMBABBqYXZhL2xhbmcvT2JqZWN0AQASamF2" + + "YS9sYW5nL1J1bm5hYmxlAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3Ry" + + "aW5nOylWAQALYXJ0L1Rlc3Q5OTYBAAlkb05vdGhpbmcAMQAHAAgAAAAAAAIAAQAJAAoAAQALAAAA" + + "HQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAAEAAEADQAOAAEACwAAADYAAgACAAAAEiu5AAIB" + + "ALIAAxIEtgAFuAAGsQAAAAEADAAAABIABAAAAAYABgAHAA4ACAARAAkAAgAPAAAAAgAQAB0AAAAK" + + "AAEABwAZABwAGQ=="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBzn3TiKGAiM0fubj25v816W0k+niqj+SQcBAAAcAAAAHhWNBIAAAAAAAAAAFgDAAAW" + + "AAAAcAAAAAoAAADIAAAAAwAAAPAAAAABAAAAFAEAAAYAAAAcAQAAAQAAAEwBAACwAgAAbAEAANoB" + + "AADiAQAA/QEAABYCAAAlAgAASQIAAGkCAACAAgAAlAIAAKoCAAC+AgAA0gIAAOACAADrAgAA7gIA" + + "APICAAD/AgAACgMAABADAAAVAwAAHgMAACMDAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" + + "CQAAAAoAAAANAAAADQAAAAkAAAAAAAAADgAAAAkAAADMAQAADgAAAAkAAADUAQAACAAEABIAAAAA" + + "AAAAAAAAAAAAAQAUAAAAAQAAABAAAAAEAAIAEwAAAAUAAAAAAAAABgAAABQAAAAAAAAAEQAAAAUA" + + "AAAAAAAACwAAALwBAABHAwAAAAAAAAIAAAA4AwAAPgMAAAEAAQABAAAAKgMAAAQAAABwEAQAAAAO" + + "AAQAAgACAAAALwMAAA4AAAByEAUAAwBiAAAAGgEBAG4gAwAQAHEAAgAAAA4AbAEAAAAAAAAAAAAA" + + "AAAAAAEAAAAGAAAAAQAAAAcABjxpbml0PgAZRG9pbmcgbm90aGluZyB0cmFuc2Zvcm1lZAAXTGFy" + + "dC9UZXN0OTk2JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk5NjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9F" + + "bmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8v" + + "UHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJM" + + "amF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAMVGVzdDk5Ni5qYXZhAAlUcmFu" + + "c2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAJZG9Ob3RoaW5nAARuYW1lAANvdXQAB3ByaW50bG4A" + + "A3J1bgAFdmFsdWUABAAHDgAGAQAHDjx4PAACAgEVGAECAwIPBBkRFwwAAAEBAIGABPgCAQGQAwAA" + + "ABAAAAAAAAAAAQAAAAAAAAABAAAAFgAAAHAAAAACAAAACgAAAMgAAAADAAAAAwAAAPAAAAAEAAAA" + + "AQAAABQBAAAFAAAABgAAABwBAAAGAAAAAQAAAEwBAAADEAAAAQAAAGwBAAABIAAAAgAAAHgBAAAG" + + "IAAAAQAAALwBAAABEAAAAgAAAMwBAAACIAAAFgAAANoBAAADIAAAAgAAACoDAAAEIAAAAgAAADgD" + + "AAAAIAAAAQAAAEcDAAAAEAAAAQAAAFgDAAA="); + + public static void notifyBreakpointReached(Thread thr, Executable e, long loc) { + int line = Breakpoint.locationToLine(e, loc); + if (line == -1 && e.getName().equals("run") && e.getDeclaringClass().equals(Transform.class)) { + // RI always reports line = -1 for obsolete methods. Just replace it with the real line for + // consistency. + line = TRANSFORM_BREAKPOINT_REDEFINED_LINE; + } + System.out.println("Breakpoint reached: " + e + " @ line=" + line); + } + + public static void run() throws Exception { + // Set up breakpoints + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + Breakpoint.startBreakpointWatch( + Test996.class, + Test996.class.getDeclaredMethod( + "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + + Transform t = new Transform(); + Method non_obsolete_run_method = Transform.class.getDeclaredMethod("run", Runnable.class); + final long obsolete_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_REDEFINED_LINE); + + System.out.println("Initially setting breakpoint to line " + TRANSFORM_BREAKPOINT_INITIAL_LINE); + long initial_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, TRANSFORM_BREAKPOINT_INITIAL_LINE); + Breakpoint.setBreakpoint(non_obsolete_run_method, initial_breakpoint_location); + + System.out.println("Running transform without redefinition."); + t.run(() -> {}); + + System.out.println("Running transform with redefinition."); + t.run(() -> { + System.out.println("Redefining calling function!"); + // This should clear the breakpoint set to TRANSFORM_BREAKPOINT_INITIAL_LINE + Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + System.out.println("Setting breakpoint on now obsolete method to line " + + TRANSFORM_BREAKPOINT_REDEFINED_LINE); + setBreakpointOnObsoleteMethod(obsolete_breakpoint_location); + }); + System.out.println("Running transform post redefinition. Should not hit any breakpoints."); + t.run(() -> {}); + + System.out.println("Setting initial breakpoint on redefined method."); + long final_breakpoint_location = + Breakpoint.lineToLocation(non_obsolete_run_method, + TRANSFORM_BREAKPOINT_POST_REDEFINITION_LINE); + Breakpoint.setBreakpoint(non_obsolete_run_method, final_breakpoint_location); + t.run(() -> {}); + + Breakpoint.stopBreakpointWatch(Thread.currentThread()); + } + + public static native void setBreakpointOnObsoleteMethod(long location); +} diff --git a/test/997-single-step/expected.txt b/test/997-single-step/expected.txt new file mode 100644 index 0000000000..69c554ca7f --- /dev/null +++ b/test/997-single-step/expected.txt @@ -0,0 +1,12 @@ +Stepping through doMultiPath(true) +Single step: public static void art.Test997.doMultiPath(boolean) @ line=41 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=42 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=43 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=47 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=48 +Stepping through doMultiPath(false) +Single step: public static void art.Test997.doMultiPath(boolean) @ line=41 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=42 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=45 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=47 +Single step: public static void art.Test997.doMultiPath(boolean) @ line=48 diff --git a/test/997-single-step/info.txt b/test/997-single-step/info.txt new file mode 100644 index 0000000000..e4a584e46f --- /dev/null +++ b/test/997-single-step/info.txt @@ -0,0 +1,3 @@ +Test basic JVMTI single step functionality. + +Ensures that we can receive single step events from JVMTI. diff --git a/test/997-single-step/run b/test/997-single-step/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/997-single-step/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/997-single-step/src/Main.java b/test/997-single-step/src/Main.java new file mode 100644 index 0000000000..1927f04d50 --- /dev/null +++ b/test/997-single-step/src/Main.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 Main { + public static void main(String[] args) throws Exception { + art.Test997.run(); + } +} diff --git a/test/997-single-step/src/art/Breakpoint.java b/test/997-single-step/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/997-single-step/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/997-single-step/src/art/Test997.java b/test/997-single-step/src/art/Test997.java new file mode 100644 index 0000000000..a7a522dcca --- /dev/null +++ b/test/997-single-step/src/art/Test997.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package art; + +import java.util.Arrays; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test997 { + static final int NO_LAST_LINE_NUMBER = -1; + static int LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER; + static Method DO_MULTIPATH_METHOD; + + static { + try { + DO_MULTIPATH_METHOD = Test997.class.getDeclaredMethod("doMultiPath", Boolean.TYPE); + } catch (Exception e) { + throw new Error("could not find method doMultiPath", e); + } + } + + // Function that acts simply to ensure there are multiple lines. + public static void doNothing() {} + + // Method with multiple paths we can break on. + public static void doMultiPath(boolean bit) { + doNothing(); + if (bit) { + doNothing(); + } else { + doNothing(); + } + doNothing(); + } + + public static void notifySingleStep(Thread thr, Executable e, long loc) { + if (!e.equals(DO_MULTIPATH_METHOD)) { + // Only report steps in doMultiPath + return; + } + int cur_line = Breakpoint.locationToLine(e, loc); + // Only report anything when the line number changes. This is so we can run this test against + // both the RI and ART and also to prevent front-end compiler changes from affecting output. + if (LAST_LINE_NUMBER == NO_LAST_LINE_NUMBER || LAST_LINE_NUMBER != cur_line) { + LAST_LINE_NUMBER = cur_line; + System.out.println("Single step: " + e + " @ line=" + cur_line); + } + } + + public static void resetTest() { + LAST_LINE_NUMBER = NO_LAST_LINE_NUMBER; + } + + public static void run() throws Exception { + boolean[] values = new boolean[] { true, false }; + Trace.enableSingleStepTracing(Test997.class, + Test997.class.getDeclaredMethod( + "notifySingleStep", Thread.class, Executable.class, Long.TYPE), + Thread.currentThread()); + for (boolean arg : values) { + System.out.println("Stepping through doMultiPath(" + arg + ")"); + resetTest(); + doMultiPath(arg); + } + + Trace.disableTracing(Thread.currentThread()); + } +} diff --git a/test/997-single-step/src/art/Trace.java b/test/997-single-step/src/art/Trace.java new file mode 100644 index 0000000000..ba3d397b0b --- /dev/null +++ b/test/997-single-step/src/art/Trace.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { + public static native void enableTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Method fieldAccess, + Method fieldModify, + Method singleStep, + Thread thr); + public static native void disableTracing(Thread thr); + + public static void enableFieldTracing(Class<?> methodClass, + Method fieldAccess, + Method fieldModify, + Thread thr) { + enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr); + } + + public static void enableMethodTracing(Class<?> methodClass, + Method entryMethod, + Method exitMethod, + Thread thr) { + enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr); + } + + public static void enableSingleStepTracing(Class<?> methodClass, + Method singleStep, + Thread thr) { + enableTracing(methodClass, null, null, null, null, singleStep, thr); + } + + public static native void watchFieldAccess(Field f); + public static native void watchFieldModification(Field f); + public static native void watchAllFieldAccesses(); + public static native void watchAllFieldModifications(); +} diff --git a/test/Android.bp b/test/Android.bp index 23ffc7e5a3..0dff01b6cf 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -281,6 +281,8 @@ art_cc_defaults { "989-method-trace-throw/method_trace.cc", "991-field-trace-2/field_trace.cc", "992-source-data/source_file.cc", + "993-breakpoints/breakpoints.cc", + "996-breakpoint-obsolete/obsolete_breakpoints.cc", ], shared_libs: [ "libbase", diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk index 60ce6c7003..753fe9a330 100644 --- a/test/Android.run-test-jvmti-java-library.mk +++ b/test/Android.run-test-jvmti-java-library.mk @@ -22,6 +22,7 @@ include $(CLEAR_VARS) LOCAL_SHIM_CLASSES := \ 902-hello-transformation/src/art/Redefinition.java \ 903-hello-tagging/src/art/Main.java \ + 989-method-trace-throw/src/art/Trace.java \ LOCAL_SRC_FILES := $(LOCAL_SHIM_CLASSES) @@ -77,6 +78,12 @@ LOCAL_SRC_FILES += \ 984-obsolete-invoke/src/art/Test984.java \ 985-re-obsolete/src/art/Test985.java \ 986-native-method-bind/src/art/Test986.java \ + 988-method-trace/src/art/Test988.java \ + 989-method-trace-throw/src/art/Test989.java \ + 990-field-trace/src/art/Test990.java \ + 991-field-trace-2/src/art/Test991.java \ + 992-source-data/src/art/Test992.java \ + 992-source-data/src/art/Target2.java \ JVMTI_RUN_TEST_GENERATED_NUMBERS := \ 901 \ @@ -119,6 +126,11 @@ JVMTI_RUN_TEST_GENERATED_NUMBERS := \ 984 \ 985 \ 986 \ + 988 \ + 989 \ + 990 \ + 991 \ + 992 \ # Try to enforce that the directories correspond to the Java files we pull in. JVMTI_RUN_TEST_DIR_CHECK := $(sort $(foreach DIR,$(JVMTI_RUN_TEST_GENERATED_NUMBERS), \ diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index d24edccd8b..ede485a81b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n" TEST_IS_NDEBUG="n" APP_IMAGE="y" JVMTI_STRESS="n" +JVMTI_STEP_STRESS="n" JVMTI_FIELD_STRESS="n" JVMTI_TRACE_STRESS="n" JVMTI_REDEFINE_STRESS="n" @@ -163,6 +164,10 @@ while true; do JVMTI_STRESS="y" JVMTI_REDEFINE_STRESS="y" shift + elif [ "x$1" = "x--jvmti-step-stress" ]; then + JVMTI_STRESS="y" + JVMTI_STEP_STRESS="y" + shift elif [ "x$1" = "x--jvmti-field-stress" ]; then JVMTI_STRESS="y" JVMTI_FIELD_STRESS="y" @@ -426,6 +431,9 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then agent_args="${agent_args},field" fi + if [[ "$JVMTI_STEP_STRESS" = "y" ]]; then + agent_args="${agent_args},step" + fi if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then agent_args="${agent_args},trace" fi diff --git a/test/knownfailures.json b/test/knownfailures.json index df8fa606f8..d3d92c651e 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -502,7 +502,7 @@ "645-checker-abs-simd", "706-checker-scheduler"], "description": ["Checker tests are not compatible with jvmti."], - "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress" }, { "tests": [ @@ -510,7 +510,7 @@ "964-default-iface-init-gen" ], "description": ["Tests that just take too long with jvmti-stress"], - "variant": "jvmti-stress | redefine-stress | trace-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | step-stress" }, { "tests": [ @@ -541,7 +541,7 @@ "981-dedup-original-dex" ], "description": ["Tests that require exact knowledge of the number of plugins and agents."], - "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress" + "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress | step-stress" }, { "tests": [ @@ -579,7 +579,7 @@ "004-ThreadStress" ], "description": "The thread stress test just takes too long with field-stress", - "variant": "jvmti-stress | field-stress" + "variant": "jvmti-stress | field-stress | step-stress" }, { "tests": [ @@ -594,16 +594,7 @@ }, { "tests": [ - "953-invoke-polymorphic-compiler" - ], - "description": "Test throws VerifyError when run with --build-with-javac-dx.", - "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, - "bug": "b/62722425" - }, - { - "tests": [ - "567-checker-compare", - "633-checker-rtp-getclass" + "567-checker-compare" ], "description": "Checker tests failing when run with --build-with-javac-dx.", "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, @@ -674,5 +665,12 @@ "description": [ "Flake on gcstress" ], "bug": "b/62562923", "variant": "gcstress & jit & target" + }, + { + "tests": ["004-JniTest"], + "description": [ "Tests failing with --build-with-javac-dx since the new annotation", + "lookup changes" ], + "bug": "b/63089991", + "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"} } ] diff --git a/test/run-test b/test/run-test index 9fe149697a..486b465a89 100755 --- a/test/run-test +++ b/test/run-test @@ -145,6 +145,7 @@ gc_verify="false" gc_stress="false" jvmti_trace_stress="false" jvmti_field_stress="false" +jvmti_step_stress="false" jvmti_redefine_stress="false" strace="false" always_clean="no" @@ -242,6 +243,9 @@ while true; do basic_verify="true" gc_stress="true" shift + elif [ "x$1" = "x--jvmti-step-stress" ]; then + jvmti_step_stress="true" + shift elif [ "x$1" = "x--jvmti-redefine-stress" ]; then jvmti_redefine_stress="true" shift @@ -464,6 +468,9 @@ fi if [ "$jvmti_redefine_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-redefine-stress" fi +if [ "$jvmti_step_stress" = "true" ]; then + run_args="${run_args} --no-app-image --jvmti-step-stress" +fi if [ "$jvmti_field_stress" = "true" ]; then run_args="${run_args} --no-app-image --jvmti-field-stress" fi @@ -679,6 +686,7 @@ if [ "$usage" = "yes" ]; then echo " --gcstress Run with gc stress testing" echo " --gcverify Run with gc verification" echo " --jvmti-trace-stress Run with jvmti method tracing stress testing" + echo " --jvmti-step-stress Run with jvmti single step stress testing" echo " --jvmti-redefine-stress" echo " Run with jvmti method redefinition stress testing" echo " --always-clean Delete the test files even if the test fails." diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index b6a5963cf4..68e1856adb 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -148,7 +148,7 @@ def gather_test_info(): VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress', - 'field-stress'} + 'field-stress', 'step-stress'} VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing', 'regalloc_gc', 'speed-profile'} @@ -445,6 +445,8 @@ def run_tests(tests): options_test += ' --jvmti-trace-stress' elif jvmti == 'redefine-stress': options_test += ' --jvmti-redefine-stress' + elif jvmti == 'step-stress': + options_test += ' --jvmti-step-stress' if address_size == '64': options_test += ' --64' @@ -965,6 +967,8 @@ def parse_option(): JVMTI_TYPES.add('redefine-stress') if options['field_stress']: JVMTI_TYPES.add('field-stress') + if options['step_stress']: + JVMTI_TYPES.add('step-stress') if options['trace_stress']: JVMTI_TYPES.add('trace-stress') if options['no_jvmti']: diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 4fe58db169..0eb71f8371 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -38,6 +38,9 @@ static void SetupCommonRetransform(); static void SetupCommonRedefine(); static void SetupCommonTransform(); +// Taken from art/runtime/modifiers.h +static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic + template <bool is_redefine> static void throwCommonRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, @@ -69,22 +72,6 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); } -namespace common_trace { - -// Taken from art/runtime/modifiers.h -static constexpr uint32_t kAccStatic = 0x0008; // field, method, ic - -struct TraceData { - jclass test_klass; - jmethodID enter_method; - jmethodID exit_method; - jmethodID field_access; - jmethodID field_modify; - bool in_callback; - bool access_watch_on_load; - bool modify_watch_on_load; -}; - static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { jint mods = 0; if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { @@ -175,6 +162,221 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv, return GetJavaValueByType(env, type[0], value); } +namespace common_breakpoint { + +struct BreakpointData { + jclass test_klass; + jmethodID breakpoint_method; + bool in_callback; + bool allow_recursive; +}; + +extern "C" void breakpointCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thread, + jmethodID method, + jlocation location) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback && !data->allow_recursive) { + return; + } + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->breakpoint_method, + thread, + method_arg, + static_cast<jlong>(location)); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Breakpoint_getLineNumberTableNative( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return nullptr; + } + jint nlines; + jvmtiLineNumberEntry* lines = nullptr; + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->GetLineNumberTable(method, &nlines, &lines))) { + return nullptr; + } + jintArray lines_array = env->NewIntArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jlongArray locs_array = env->NewLongArray(nlines); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + ScopedLocalRef<jclass> object_class(env, env->FindClass("java/lang/Object")); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jobjectArray ret = env->NewObjectArray(2, object_class.get(), nullptr); + if (env->ExceptionCheck()) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return nullptr; + } + jint* temp_lines = env->GetIntArrayElements(lines_array, /*isCopy*/nullptr); + jlong* temp_locs = env->GetLongArrayElements(locs_array, /*isCopy*/nullptr); + for (jint i = 0; i < nlines; i++) { + temp_lines[i] = lines[i].line_number; + temp_locs[i] = lines[i].start_location; + } + env->ReleaseIntArrayElements(lines_array, temp_lines, 0); + env->ReleaseLongArrayElements(locs_array, temp_locs, 0); + env->SetObjectArrayElement(ret, 0, locs_array); + env->SetObjectArrayElement(ret, 1, lines_array); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(lines)); + return ret; +} + +extern "C" JNIEXPORT jlong JNICALL Java_art_Breakpoint_getStartLocation(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return 0; + } + jlong start = 0; + jlong end = end; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodLocation(method, &start, &end)); + return start; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_clearBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->ClearBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_setBreakpoint(JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jobject target, + jlocation location) { + jmethodID method = env->FromReflectedMethod(target); + if (env->ExceptionCheck()) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(method, location)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jclass method_klass, + jobject method, + jboolean allow_recursive, + jthread thr) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(BreakpointData), + reinterpret_cast<unsigned char**>(&data)))) { + return; + } + memset(data, 0, sizeof(BreakpointData)); + data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass)); + data->breakpoint_method = env->FromReflectedMethod(method); + data->in_callback = false; + data->allow_recursive = allow_recursive; + + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { + return; + } + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.Breakpoint = breakpointCB; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_stopBreakpointWatch( + JNIEnv* env, + jclass k ATTRIBUTE_UNUSED, + jthread thr) { + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } +} + +} // namespace common_breakpoint + +namespace common_trace { + +struct TraceData { + jclass test_klass; + jmethodID enter_method; + jmethodID exit_method; + jmethodID field_access; + jmethodID field_modify; + jmethodID single_step; + bool in_callback; + bool access_watch_on_load; + bool modify_watch_on_load; +}; + +static void singleStepCB(jvmtiEnv* jvmti, + JNIEnv* jnienv, + jthread thread, + jmethodID method, + jlocation location) { + TraceData* data = nullptr; + if (JvmtiErrorToException(jnienv, jvmti, + jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { + return; + } + if (data->in_callback) { + return; + } + CHECK(data->single_step != nullptr); + data->in_callback = true; + jobject method_arg = GetJavaMethod(jvmti, jnienv, method); + jnienv->CallStaticVoidMethod(data->test_klass, + data->single_step, + thread, + method_arg, + static_cast<jlong>(location)); + jnienv->DeleteLocalRef(method_arg); + data->in_callback = false; +} + static void fieldAccessCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jthread thr ATTRIBUTE_UNUSED, @@ -481,6 +683,7 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( jobject exit, jobject field_access, jobject field_modify, + jobject single_step, jthread thr) { TraceData* data = nullptr; if (JvmtiErrorToException(env, @@ -495,8 +698,17 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr; + data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr; data->in_callback = false; + void* old_data = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) { + return; + } else if (old_data != nullptr) { + ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException")); + env->ThrowNew(rt_exception.get(), "Environment already has local storage set!"); + return; + } if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { return; } @@ -508,6 +720,7 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( cb.FieldAccess = fieldAccessCB; cb.FieldModification = fieldModificationCB; cb.ClassPrepare = classPrepareCB; + cb.SingleStep = singleStepCB; if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { return; } @@ -543,6 +756,14 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing( thr))) { return; } + if (single_step != nullptr && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } } extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( @@ -571,6 +792,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing( thr))) { return; } + if (JvmtiErrorToException(env, jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } } } // namespace common_trace diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index 40fcc4f11d..d197acd216 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -17,6 +17,7 @@ #include <jni.h> #include <stdio.h> #include <iostream> +#include <iomanip> #include <fstream> #include <memory> #include <stdio.h> @@ -40,6 +41,7 @@ struct StressData { bool trace_stress; bool redefine_stress; bool field_stress; + bool step_stress; }; static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { @@ -586,6 +588,21 @@ void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv, } } +void JNICALL SingleStepHook(jvmtiEnv* jvmtienv, + JNIEnv* env, + jthread thread, + jmethodID method, + jlocation location) { + ScopedThreadInfo info(jvmtienv, env, thread); + ScopedMethodInfo method_info(jvmtienv, env, method); + if (!method_info.Init()) { + LOG(ERROR) << "Unable to get method info!"; + return; + } + LOG(INFO) << "Single step at location: 0x" << std::setw(8) << std::setfill('0') << std::hex + << location << " in method " << method_info << " thread: " << info.GetName(); +} + // The hook we are using. void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti, JNIEnv* jni_env ATTRIBUTE_UNUSED, @@ -645,6 +662,8 @@ static void ReadOptions(StressData* data, char* options) { std::string cur = GetOption(ops); if (cur == "trace") { data->trace_stress = true; + } else if (cur == "step") { + data->step_stress = true; } else if (cur == "field") { data->field_stress = true; } else if (cur == "redefine") { @@ -776,6 +795,7 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, cb.FieldAccess = FieldAccessHook; cb.FieldModification = FieldModificationHook; cb.ClassPrepare = ClassPrepareHook; + cb.SingleStep = SingleStepHook; if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set class file load hook cb!"; return 1; @@ -837,6 +857,13 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, return 1; } } + if (data->step_stress) { + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + nullptr) != JVMTI_ERROR_NONE) { + return 1; + } + } return 0; } diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index bf7692ab15..75694c340c 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -68,7 +68,7 @@ if $using_jack; then fi if [[ $mode == "host" ]]; then - make_command="make $j_arg $showcommands build-art-host-tests $common_targets" + make_command="make $j_arg $showcommands build-art-host-tests $common_targets dx-tests" make_command+=" ${out_dir}/host/linux-x86/lib/libjavacoretests.so " make_command+=" ${out_dir}/host/linux-x86/lib64/libjavacoretests.so" elif [[ $mode == "target" ]]; then |