diff options
28 files changed, 663 insertions, 375 deletions
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 049b3e3a40..130f0e970f 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -508,19 +508,14 @@ void CodeGenerator::BuildNativeGCMap( dex_compilation_unit.GetVerifiedMethod()->GetDexGcMap(); verifier::DexPcToReferenceMap dex_gc_map(&(gc_map_raw)[0]); - uint32_t max_native_offset = 0; - for (size_t i = 0; i < pc_infos_.Size(); i++) { - uint32_t native_offset = pc_infos_.Get(i).native_pc; - if (native_offset > max_native_offset) { - max_native_offset = native_offset; - } - } - - GcMapBuilder builder(data, pc_infos_.Size(), max_native_offset, dex_gc_map.RegWidth()); - for (size_t i = 0; i < pc_infos_.Size(); i++) { - struct PcInfo pc_info = pc_infos_.Get(i); - uint32_t native_offset = pc_info.native_pc; - uint32_t dex_pc = pc_info.dex_pc; + uint32_t max_native_offset = stack_map_stream_.ComputeMaxNativePcOffset(); + + size_t num_stack_maps = stack_map_stream_.GetNumberOfStackMaps(); + GcMapBuilder builder(data, num_stack_maps, max_native_offset, dex_gc_map.RegWidth()); + for (size_t i = 0; i != num_stack_maps; ++i) { + const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); + uint32_t native_offset = stack_map_entry.native_pc_offset; + uint32_t dex_pc = stack_map_entry.dex_pc; const uint8_t* references = dex_gc_map.FindBitMap(dex_pc, false); CHECK(references != nullptr) << "Missing ref for dex pc 0x" << std::hex << dex_pc; builder.AddEntry(native_offset, references); @@ -528,17 +523,17 @@ void CodeGenerator::BuildNativeGCMap( } void CodeGenerator::BuildSourceMap(DefaultSrcMap* src_map) const { - for (size_t i = 0; i < pc_infos_.Size(); i++) { - struct PcInfo pc_info = pc_infos_.Get(i); - uint32_t pc2dex_offset = pc_info.native_pc; - int32_t pc2dex_dalvik_offset = pc_info.dex_pc; + for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) { + const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); + uint32_t pc2dex_offset = stack_map_entry.native_pc_offset; + int32_t pc2dex_dalvik_offset = stack_map_entry.dex_pc; src_map->push_back(SrcMapElem({pc2dex_offset, pc2dex_dalvik_offset})); } } void CodeGenerator::BuildMappingTable(std::vector<uint8_t>* data) const { uint32_t pc2dex_data_size = 0u; - uint32_t pc2dex_entries = pc_infos_.Size(); + uint32_t pc2dex_entries = stack_map_stream_.GetNumberOfStackMaps(); uint32_t pc2dex_offset = 0u; int32_t pc2dex_dalvik_offset = 0; uint32_t dex2pc_data_size = 0u; @@ -547,11 +542,11 @@ void CodeGenerator::BuildMappingTable(std::vector<uint8_t>* data) const { int32_t dex2pc_dalvik_offset = 0; for (size_t i = 0; i < pc2dex_entries; i++) { - struct PcInfo pc_info = pc_infos_.Get(i); - pc2dex_data_size += UnsignedLeb128Size(pc_info.native_pc - pc2dex_offset); - pc2dex_data_size += SignedLeb128Size(pc_info.dex_pc - pc2dex_dalvik_offset); - pc2dex_offset = pc_info.native_pc; - pc2dex_dalvik_offset = pc_info.dex_pc; + const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); + pc2dex_data_size += UnsignedLeb128Size(stack_map_entry.native_pc_offset - pc2dex_offset); + pc2dex_data_size += SignedLeb128Size(stack_map_entry.dex_pc - pc2dex_dalvik_offset); + pc2dex_offset = stack_map_entry.native_pc_offset; + pc2dex_dalvik_offset = stack_map_entry.dex_pc; } // Walk over the blocks and find which ones correspond to catch block entries. @@ -586,12 +581,12 @@ void CodeGenerator::BuildMappingTable(std::vector<uint8_t>* data) const { dex2pc_dalvik_offset = 0u; for (size_t i = 0; i < pc2dex_entries; i++) { - struct PcInfo pc_info = pc_infos_.Get(i); - DCHECK(pc2dex_offset <= pc_info.native_pc); - write_pos = EncodeUnsignedLeb128(write_pos, pc_info.native_pc - pc2dex_offset); - write_pos = EncodeSignedLeb128(write_pos, pc_info.dex_pc - pc2dex_dalvik_offset); - pc2dex_offset = pc_info.native_pc; - pc2dex_dalvik_offset = pc_info.dex_pc; + const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); + DCHECK(pc2dex_offset <= stack_map_entry.native_pc_offset); + write_pos = EncodeUnsignedLeb128(write_pos, stack_map_entry.native_pc_offset - pc2dex_offset); + write_pos = EncodeSignedLeb128(write_pos, stack_map_entry.dex_pc - pc2dex_dalvik_offset); + pc2dex_offset = stack_map_entry.native_pc_offset; + pc2dex_dalvik_offset = stack_map_entry.dex_pc; } for (size_t i = 0; i < graph_->GetBlocks().Size(); ++i) { @@ -617,9 +612,9 @@ void CodeGenerator::BuildMappingTable(std::vector<uint8_t>* data) const { auto it = table.PcToDexBegin(); auto it2 = table.DexToPcBegin(); for (size_t i = 0; i < pc2dex_entries; i++) { - struct PcInfo pc_info = pc_infos_.Get(i); - CHECK_EQ(pc_info.native_pc, it.NativePcOffset()); - CHECK_EQ(pc_info.dex_pc, it.DexPc()); + const StackMapStream::StackMapEntry& stack_map_entry = stack_map_stream_.GetStackMap(i); + CHECK_EQ(stack_map_entry.native_pc_offset, it.NativePcOffset()); + CHECK_EQ(stack_map_entry.dex_pc, it.DexPc()); ++it; } for (size_t i = 0; i < graph_->GetBlocks().Size(); ++i) { @@ -695,14 +690,11 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, } // Collect PC infos for the mapping table. - struct PcInfo pc_info; - pc_info.dex_pc = outer_dex_pc; - pc_info.native_pc = GetAssembler()->CodeSize(); - pc_infos_.Add(pc_info); + uint32_t native_pc = GetAssembler()->CodeSize(); if (instruction == nullptr) { // For stack overflow checks. - stack_map_stream_.BeginStackMapEntry(pc_info.dex_pc, pc_info.native_pc, 0, 0, 0, 0); + stack_map_stream_.BeginStackMapEntry(outer_dex_pc, native_pc, 0, 0, 0, 0); stack_map_stream_.EndStackMapEntry(); return; } @@ -719,8 +711,8 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, } // The register mask must be a subset of callee-save registers. DCHECK_EQ(register_mask & core_callee_save_mask_, register_mask); - stack_map_stream_.BeginStackMapEntry(pc_info.dex_pc, - pc_info.native_pc, + stack_map_stream_.BeginStackMapEntry(outer_dex_pc, + native_pc, register_mask, locations->GetStackMask(), outer_environment_size, diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index c6ebf6dbd8..e6b1f7c6aa 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -64,11 +64,6 @@ class CodeAllocator { DISALLOW_COPY_AND_ASSIGN(CodeAllocator); }; -struct PcInfo { - uint32_t dex_pc; - uintptr_t native_pc; -}; - class SlowPathCode : public ArenaObject<kArenaAllocSlowPaths> { public: SlowPathCode() { @@ -366,7 +361,6 @@ class CodeGenerator { is_baseline_(false), graph_(graph), compiler_options_(compiler_options), - pc_infos_(graph->GetArena(), 32), slow_paths_(graph->GetArena(), 8), block_order_(nullptr), current_block_index_(0), @@ -455,7 +449,6 @@ class CodeGenerator { HGraph* const graph_; const CompilerOptions& compiler_options_; - GrowableArray<PcInfo> pc_infos_; GrowableArray<SlowPathCode*> slow_paths_; // The order to use for code generation. diff --git a/compiler/optimizing/instruction_simplifier.h b/compiler/optimizing/instruction_simplifier.h index 024462081f..668956a614 100644 --- a/compiler/optimizing/instruction_simplifier.h +++ b/compiler/optimizing/instruction_simplifier.h @@ -36,6 +36,9 @@ class InstructionSimplifier : public HOptimization { static constexpr const char* kInstructionSimplifierPassName = "instruction_simplifier"; void Run() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(InstructionSimplifier); }; } // namespace art diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 126b3b9879..7ef69559de 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -2483,7 +2483,7 @@ class HInvoke : public HInstruction { intrinsic_ = intrinsic; } - bool IsInlined() const { + bool IsFromInlinedInvoke() const { return GetEnvironment()->GetParent() != nullptr; } @@ -3603,7 +3603,7 @@ class HLoadClass : public HExpression<1> { bool CanThrow() const OVERRIDE { // May call runtime and and therefore can throw. // TODO: finer grain decision. - return !is_referrers_class_; + return CanCallRuntime(); } ReferenceTypeInfo GetLoadedClassRTI() { diff --git a/compiler/optimizing/optimization.h b/compiler/optimizing/optimization.h index ccf8de9f6a..2d1c0ba9f9 100644 --- a/compiler/optimizing/optimization.h +++ b/compiler/optimizing/optimization.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_OPTIMIZING_OPTIMIZATION_H_ #define ART_COMPILER_OPTIMIZING_OPTIMIZATION_H_ +#include "base/arena_object.h" #include "nodes.h" #include "optimizing_compiler_stats.h" @@ -25,7 +26,7 @@ namespace art { /** * Abstraction to implement an optimization pass. */ -class HOptimization : public ValueObject { +class HOptimization : public ArenaObject<kArenaAllocMisc> { public: HOptimization(HGraph* graph, bool is_in_ssa_form, diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index bf0b9fac0f..303a7cb1fd 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -318,49 +318,55 @@ static void RunOptimizations(HGraph* graph, const DexCompilationUnit& dex_compilation_unit, PassInfoPrinter* pass_info_printer, StackHandleScopeCollection* handles) { - HDeadCodeElimination dce1(graph, stats, - HDeadCodeElimination::kInitialDeadCodeEliminationPassName); - HDeadCodeElimination dce2(graph, stats, - HDeadCodeElimination::kFinalDeadCodeEliminationPassName); - HConstantFolding fold1(graph); - InstructionSimplifier simplify1(graph, stats); - HBooleanSimplifier boolean_simplify(graph); - - HInliner inliner(graph, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); - - HConstantFolding fold2(graph, "constant_folding_after_inlining"); - SideEffectsAnalysis side_effects(graph); - GVNOptimization gvn(graph, side_effects); - LICM licm(graph, side_effects); - BoundsCheckElimination bce(graph); - ReferenceTypePropagation type_propagation(graph, handles); - InstructionSimplifier simplify2(graph, stats, "instruction_simplifier_after_types"); - InstructionSimplifier simplify3(graph, stats, "last_instruction_simplifier"); - ReferenceTypePropagation type_propagation2(graph, handles); - - IntrinsicsRecognizer intrinsics(graph, driver); + ArenaAllocator* arena = graph->GetArena(); + HDeadCodeElimination* dce1 = new (arena) HDeadCodeElimination( + graph, stats, HDeadCodeElimination::kInitialDeadCodeEliminationPassName); + HDeadCodeElimination* dce2 = new (arena) HDeadCodeElimination( + graph, stats, HDeadCodeElimination::kFinalDeadCodeEliminationPassName); + HConstantFolding* fold1 = new (arena) HConstantFolding(graph); + InstructionSimplifier* simplify1 = new (arena) InstructionSimplifier(graph, stats); + HBooleanSimplifier* boolean_simplify = new (arena) HBooleanSimplifier(graph); + + HInliner* inliner = new (arena) HInliner( + graph, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); + + HConstantFolding* fold2 = new (arena) HConstantFolding(graph, "constant_folding_after_inlining"); + SideEffectsAnalysis* side_effects = new (arena) SideEffectsAnalysis(graph); + GVNOptimization* gvn = new (arena) GVNOptimization(graph, *side_effects); + LICM* licm = new (arena) LICM(graph, *side_effects); + BoundsCheckElimination* bce = new (arena) BoundsCheckElimination(graph); + ReferenceTypePropagation* type_propagation = + new (arena) ReferenceTypePropagation(graph, handles); + InstructionSimplifier* simplify2 = new (arena) InstructionSimplifier( + graph, stats, "instruction_simplifier_after_types"); + InstructionSimplifier* simplify3 = new (arena) InstructionSimplifier( + graph, stats, "last_instruction_simplifier"); + ReferenceTypePropagation* type_propagation2 = + new (arena) ReferenceTypePropagation(graph, handles); + + IntrinsicsRecognizer* intrinsics = new (arena) IntrinsicsRecognizer(graph, driver); HOptimization* optimizations[] = { - &intrinsics, - &dce1, - &fold1, - &simplify1, - &type_propagation, - &simplify2, - &inliner, + intrinsics, + dce1, + fold1, + simplify1, + type_propagation, + simplify2, + inliner, // Run another type propagation phase: inlining will open up more opprotunities // to remove checkast/instanceof and null checks. - &type_propagation2, + type_propagation2, // BooleanSimplifier depends on the InstructionSimplifier removing redundant // suspend checks to recognize empty blocks. - &boolean_simplify, - &fold2, - &side_effects, - &gvn, - &licm, - &bce, - &simplify3, - &dce2, + boolean_simplify, + fold2, + side_effects, + gvn, + licm, + bce, + simplify3, + dce2, }; RunOptimizations(optimizations, arraysize(optimizations), pass_info_printer); diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index a249aa9711..ca928ae0f2 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -86,16 +86,6 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire DCHECK(last_input != nullptr) << "Last input is not HLoadClass. It is " << last_input->DebugName(); - // The static call will initialize the class so there's no need for a clinit check if - // it's the first user. - // There is one special case where we still need the clinit check, when inlining. Because - // currently the callee is responsible for reporting parameters to the GC, the code - // that walks the stack during `artQuickResolutionTrampoline` cannot be interrupted for GC. - // Therefore we cannot allocate any object in that code, including loading a new class. - if (last_input == invoke->GetPrevious() && !invoke->IsInlined()) { - last_input->SetMustGenerateClinitCheck(false); - } - // Remove a load class instruction as last input of a static // invoke, which has been added (along with a clinit check, // removed by PrepareForRegisterAllocation::VisitClinitCheck @@ -104,10 +94,20 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire // stage (i.e., after inlining has been performed). invoke->RemoveLoadClassAsLastInput(); - // If the load class instruction is no longer used, remove it from - // the graph. - if (!last_input->HasUses() && !(last_input->MustGenerateClinitCheck() && invoke->IsInlined())) { - last_input->GetBlock()->RemoveInstruction(last_input); + // The static call will initialize the class so there's no need for a clinit check if + // it's the first user. + // There is one special case where we still need the clinit check, when inlining. Because + // currently the callee is responsible for reporting parameters to the GC, the code + // that walks the stack during `artQuickResolutionTrampoline` cannot be interrupted for GC. + // Therefore we cannot allocate any object in that code, including loading a new class. + if (last_input == invoke->GetPrevious() && !invoke->IsFromInlinedInvoke()) { + last_input->SetMustGenerateClinitCheck(false); + + // If the load class instruction is no longer used, remove it from + // the graph. + if (!last_input->HasUses()) { + last_input->GetBlock()->RemoveInstruction(last_input); + } } } } diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc index 42b9182d55..65610d54a6 100644 --- a/compiler/optimizing/stack_map_stream.cc +++ b/compiler/optimizing/stack_map_stream.cc @@ -49,7 +49,6 @@ void StackMapStream::BeginStackMapEntry(uint32_t dex_pc, } dex_pc_max_ = std::max(dex_pc_max_, dex_pc); - native_pc_offset_max_ = std::max(native_pc_offset_max_, native_pc_offset); register_mask_max_ = std::max(register_mask_max_, register_mask); current_dex_register_ = 0; } @@ -128,16 +127,25 @@ void StackMapStream::EndInlineInfoEntry() { current_inline_info_ = InlineInfoEntry(); } +uint32_t StackMapStream::ComputeMaxNativePcOffset() const { + uint32_t max_native_pc_offset = 0u; + for (size_t i = 0, size = stack_maps_.Size(); i != size; ++i) { + max_native_pc_offset = std::max(max_native_pc_offset, stack_maps_.Get(i).native_pc_offset); + } + return max_native_pc_offset; +} + size_t StackMapStream::PrepareForFillIn() { int stack_mask_number_of_bits = stack_mask_max_ + 1; // Need room for max element too. stack_mask_size_ = RoundUp(stack_mask_number_of_bits, kBitsPerByte) / kBitsPerByte; inline_info_size_ = ComputeInlineInfoSize(); dex_register_maps_size_ = ComputeDexRegisterMapsSize(); + uint32_t max_native_pc_offset = ComputeMaxNativePcOffset(); stack_map_encoding_ = StackMapEncoding::CreateFromSizes(stack_mask_size_, inline_info_size_, dex_register_maps_size_, dex_pc_max_, - native_pc_offset_max_, + max_native_pc_offset, register_mask_max_); stack_maps_size_ = stack_maps_.Size() * stack_map_encoding_.ComputeStackMapSize(); dex_register_location_catalog_size_ = ComputeDexRegisterLocationCatalogSize(); diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h index 274d573350..bc3653d7ea 100644 --- a/compiler/optimizing/stack_map_stream.h +++ b/compiler/optimizing/stack_map_stream.h @@ -67,7 +67,6 @@ class StackMapStream : public ValueObject { inline_infos_(allocator, 2), stack_mask_max_(-1), dex_pc_max_(0), - native_pc_offset_max_(0), register_mask_max_(0), number_of_stack_maps_with_inline_info_(0), dex_map_hash_to_stack_map_indices_(std::less<uint32_t>(), allocator->Adapter()), @@ -126,6 +125,17 @@ class StackMapStream : public ValueObject { uint32_t num_dex_registers); void EndInlineInfoEntry(); + size_t GetNumberOfStackMaps() const { + return stack_maps_.Size(); + } + + const StackMapEntry& GetStackMap(size_t i) const { + DCHECK_LT(i, stack_maps_.Size()); + return stack_maps_.GetRawStorage()[i]; + } + + uint32_t ComputeMaxNativePcOffset() const; + // Prepares the stream to fill in a memory region. Must be called before FillIn. // Returns the size (in bytes) needed to store this stream. size_t PrepareForFillIn(); @@ -163,7 +173,6 @@ class StackMapStream : public ValueObject { GrowableArray<InlineInfoEntry> inline_infos_; int stack_mask_max_; uint32_t dex_pc_max_; - uint32_t native_pc_offset_max_; uint32_t register_mask_max_; size_t number_of_stack_maps_with_inline_info_; diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index f2be85e277..0ab148e37e 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -94,7 +94,6 @@ enum LockLevel { kMonitorListLock, kJniLoadLibraryLock, kThreadListLock, - kBreakpointInvokeLock, kAllocTrackerLock, kDeoptimizationLock, kProfilerLock, diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 3c75daf7b0..b2e40b4e92 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -33,6 +33,7 @@ #include "gc/space/large_object_space.h" #include "gc/space/space-inl.h" #include "handle_scope.h" +#include "jdwp/jdwp_priv.h" #include "jdwp/object_registry.h" #include "mirror/class.h" #include "mirror/class-inl.h" @@ -3660,17 +3661,16 @@ static char JdwpTagToShortyChar(JDWP::JdwpTag tag) { } } -JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId object_id, - JDWP::RefTypeId class_id, JDWP::MethodId method_id, - uint32_t arg_count, uint64_t* arg_values, - JDWP::JdwpTag* arg_types, uint32_t options, - JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, - JDWP::ObjectId* pExceptionId) { - ThreadList* thread_list = Runtime::Current()->GetThreadList(); +JDWP::JdwpError Dbg::PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thread_id, + JDWP::ObjectId object_id, JDWP::RefTypeId class_id, + JDWP::MethodId method_id, uint32_t arg_count, + uint64_t arg_values[], JDWP::JdwpTag* arg_types, + uint32_t options) { + Thread* const self = Thread::Current(); + CHECK_EQ(self, GetDebugThread()) << "This must be called by the JDWP thread"; + ThreadList* thread_list = Runtime::Current()->GetThreadList(); Thread* targetThread = nullptr; - std::unique_ptr<DebugInvokeReq> req; - Thread* self = Thread::Current(); { ScopedObjectAccessUnchecked soa(self); JDWP::JdwpError error; @@ -3780,99 +3780,82 @@ JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId objec } // Allocates a DebugInvokeReq. - req.reset(new (std::nothrow) DebugInvokeReq(receiver, c, m, options, arg_values, arg_count)); - if (req.get() == nullptr) { + DebugInvokeReq* req = new (std::nothrow) DebugInvokeReq(request_id, thread_id, receiver, c, m, + options, arg_values, arg_count); + if (req == nullptr) { LOG(ERROR) << "Failed to allocate DebugInvokeReq"; return JDWP::ERR_OUT_OF_MEMORY; } - // Attach the DebugInvokeReq to the target thread so it executes the method when - // it is resumed. Once the invocation completes, it will detach it and signal us - // before suspending itself. - targetThread->SetDebugInvokeReq(req.get()); + // Attaches the DebugInvokeReq to the target thread so it executes the method when + // it is resumed. Once the invocation completes, the target thread will delete it before + // suspending itself (see ThreadList::SuspendSelfForDebugger). + targetThread->SetDebugInvokeReq(req); } // The fact that we've released the thread list lock is a bit risky --- if the thread goes - // away we're sitting high and dry -- but we must release this before the ResumeAllThreads - // call, and it's unwise to hold it during WaitForSuspend. - - { - /* - * We change our (JDWP thread) status, which should be THREAD_RUNNING, - * so we can suspend for a GC if the invoke request causes us to - * run out of memory. It's also a good idea to change it before locking - * the invokeReq mutex, although that should never be held for long. - */ - self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend); - - VLOG(jdwp) << " Transferring control to event thread"; - { - MutexLock mu(self, req->lock); - - if ((options & JDWP::INVOKE_SINGLE_THREADED) == 0) { - VLOG(jdwp) << " Resuming all threads"; - thread_list->UndoDebuggerSuspensions(); - } else { - VLOG(jdwp) << " Resuming event thread only"; - thread_list->Resume(targetThread, true); - } - - // The target thread is resumed but needs the JDWP token we're holding. - // We release it now and will acquire it again when the invocation is - // complete and the target thread suspends itself. - gJdwpState->ReleaseJdwpTokenForCommand(); - - // Wait for the request to finish executing. - while (targetThread->GetInvokeReq() != nullptr) { - req->cond.Wait(self); - } - } - VLOG(jdwp) << " Control has returned from event thread"; - - /* wait for thread to re-suspend itself */ - SuspendThread(thread_id, false /* request_suspension */); - - // Now the thread is suspended again, we can re-acquire the JDWP token. - gJdwpState->AcquireJdwpTokenForCommand(); + // away we're sitting high and dry -- but we must release this before the UndoDebuggerSuspensions + // call. - self->TransitionFromSuspendedToRunnable(); - } - - /* - * Suspend the threads. We waited for the target thread to suspend - * itself, so all we need to do is suspend the others. - * - * The SuspendAllForDebugger() call will double-suspend the event thread, - * so we want to resume the target thread once to keep the books straight. - */ if ((options & JDWP::INVOKE_SINGLE_THREADED) == 0) { - self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSuspension); - VLOG(jdwp) << " Suspending all threads"; - thread_list->SuspendAllForDebugger(); - self->TransitionFromSuspendedToRunnable(); - VLOG(jdwp) << " Resuming event thread to balance the count"; + VLOG(jdwp) << " Resuming all threads"; + thread_list->UndoDebuggerSuspensions(); + } else { + VLOG(jdwp) << " Resuming event thread only"; thread_list->Resume(targetThread, true); } - // Copy the result. - *pResultTag = req->result_tag; - *pResultValue = req->result_value; - *pExceptionId = req->exception; - return req->error; + return JDWP::ERR_NONE; } void Dbg::ExecuteMethod(DebugInvokeReq* pReq) { - ScopedObjectAccess soa(Thread::Current()); + Thread* const self = Thread::Current(); + CHECK_NE(self, GetDebugThread()) << "This must be called by the event thread"; + + ScopedObjectAccess soa(self); // We can be called while an exception is pending. We need // to preserve that across the method invocation. - StackHandleScope<3> hs(soa.Self()); - auto old_exception = hs.NewHandle<mirror::Throwable>(soa.Self()->GetException()); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::Throwable> old_exception = hs.NewHandle(soa.Self()->GetException()); soa.Self()->ClearException(); + // Execute the method then sends reply to the debugger. + ExecuteMethodWithoutPendingException(soa, pReq); + + // If an exception was pending before the invoke, restore it now. + if (old_exception.Get() != nullptr) { + soa.Self()->SetException(old_exception.Get()); + } +} + +// Helper function: write a variable-width value into the output input buffer. +static void WriteValue(JDWP::ExpandBuf* pReply, int width, uint64_t value) { + switch (width) { + case 1: + expandBufAdd1(pReply, value); + break; + case 2: + expandBufAdd2BE(pReply, value); + break; + case 4: + expandBufAdd4BE(pReply, value); + break; + case 8: + expandBufAdd8BE(pReply, value); + break; + default: + LOG(FATAL) << width; + UNREACHABLE(); + } +} + +void Dbg::ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) { + soa.Self()->AssertNoPendingException(); + // Translate the method through the vtable, unless the debugger wants to suppress it. - auto* m = pReq->method; - auto image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); + ArtMethod* m = pReq->method; + size_t image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); if ((pReq->options & JDWP::INVOKE_NONVIRTUAL) == 0 && pReq->receiver.Read() != nullptr) { ArtMethod* actual_method = pReq->klass.Read()->FindVirtualMethodForVirtualOrInterface(m, image_pointer_size); @@ -3889,39 +3872,133 @@ void Dbg::ExecuteMethod(DebugInvokeReq* pReq) { CHECK_EQ(sizeof(jvalue), sizeof(uint64_t)); + // Invoke the method. ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(pReq->receiver.Read())); JValue result = InvokeWithJValues(soa, ref.get(), soa.EncodeMethod(m), - reinterpret_cast<jvalue*>(pReq->arg_values)); + reinterpret_cast<jvalue*>(pReq->arg_values.get())); - pReq->result_tag = BasicTagFromDescriptor(m->GetShorty()); - const bool is_object_result = (pReq->result_tag == JDWP::JT_OBJECT); + // Prepare JDWP ids for the reply. + JDWP::JdwpTag result_tag = BasicTagFromDescriptor(m->GetShorty()); + const bool is_object_result = (result_tag == JDWP::JT_OBJECT); + StackHandleScope<2> hs(soa.Self()); Handle<mirror::Object> object_result = hs.NewHandle(is_object_result ? result.GetL() : nullptr); Handle<mirror::Throwable> exception = hs.NewHandle(soa.Self()->GetException()); soa.Self()->ClearException(); - pReq->exception = gRegistry->Add(exception); - if (pReq->exception != 0) { + + if (!IsDebuggerActive()) { + // The debugger detached: we must not re-suspend threads. We also don't need to fill the reply + // because it won't be sent either. + return; + } + + JDWP::ObjectId exceptionObjectId = gRegistry->Add(exception); + uint64_t result_value = 0; + if (exceptionObjectId != 0) { VLOG(jdwp) << " JDWP invocation returning with exception=" << exception.Get() << " " << exception->Dump(); - pReq->result_value = 0; + result_value = 0; } else if (is_object_result) { - /* if no exception thrown, examine object result more closely */ + /* if no exception was thrown, examine object result more closely */ JDWP::JdwpTag new_tag = TagFromObject(soa, object_result.Get()); - if (new_tag != pReq->result_tag) { - VLOG(jdwp) << " JDWP promoted result from " << pReq->result_tag << " to " << new_tag; - pReq->result_tag = new_tag; + if (new_tag != result_tag) { + VLOG(jdwp) << " JDWP promoted result from " << result_tag << " to " << new_tag; + result_tag = new_tag; } // Register the object in the registry and reference its ObjectId. This ensures // GC safety and prevents from accessing stale reference if the object is moved. - pReq->result_value = gRegistry->Add(object_result.Get()); + result_value = gRegistry->Add(object_result.Get()); } else { // Primitive result. - DCHECK(IsPrimitiveTag(pReq->result_tag)); - pReq->result_value = result.GetJ(); + DCHECK(IsPrimitiveTag(result_tag)); + result_value = result.GetJ(); + } + const bool is_constructor = m->IsConstructor() && !m->IsStatic(); + if (is_constructor) { + // If we invoked a constructor (which actually returns void), return the receiver, + // unless we threw, in which case we return null. + result_tag = JDWP::JT_OBJECT; + if (exceptionObjectId == 0) { + // TODO we could keep the receiver ObjectId in the DebugInvokeReq to avoid looking into the + // object registry. + result_value = GetObjectRegistry()->Add(pReq->receiver.Read()); + } else { + result_value = 0; + } } - if (old_exception.Get() != nullptr) { - soa.Self()->SetException(old_exception.Get()); + // Suspend other threads if the invoke is not single-threaded. + if ((pReq->options & JDWP::INVOKE_SINGLE_THREADED) == 0) { + soa.Self()->TransitionFromRunnableToSuspended(kWaitingForDebuggerSuspension); + VLOG(jdwp) << " Suspending all threads"; + Runtime::Current()->GetThreadList()->SuspendAllForDebugger(); + soa.Self()->TransitionFromSuspendedToRunnable(); + } + + VLOG(jdwp) << " --> returned " << result_tag + << StringPrintf(" %#" PRIx64 " (except=%#" PRIx64 ")", result_value, + exceptionObjectId); + + // Show detailed debug output. + if (result_tag == JDWP::JT_STRING && exceptionObjectId == 0) { + if (result_value != 0) { + if (VLOG_IS_ON(jdwp)) { + std::string result_string; + JDWP::JdwpError error = Dbg::StringToUtf8(result_value, &result_string); + CHECK_EQ(error, JDWP::ERR_NONE); + VLOG(jdwp) << " string '" << result_string << "'"; + } + } else { + VLOG(jdwp) << " string (null)"; + } + } + + // Attach the reply to DebugInvokeReq so it can be sent to the debugger when the event thread + // is ready to suspend. + BuildInvokeReply(pReq->reply, pReq->request_id, result_tag, result_value, exceptionObjectId); +} + +void Dbg::BuildInvokeReply(JDWP::ExpandBuf* pReply, uint32_t request_id, JDWP::JdwpTag result_tag, + uint64_t result_value, JDWP::ObjectId exception) { + // Make room for the JDWP header since we do not know the size of the reply yet. + JDWP::expandBufAddSpace(pReply, kJDWPHeaderLen); + + size_t width = GetTagWidth(result_tag); + JDWP::expandBufAdd1(pReply, result_tag); + if (width != 0) { + WriteValue(pReply, width, result_value); + } + JDWP::expandBufAdd1(pReply, JDWP::JT_OBJECT); + JDWP::expandBufAddObjectId(pReply, exception); + + // Now we know the size, we can complete the JDWP header. + uint8_t* buf = expandBufGetBuffer(pReply); + JDWP::Set4BE(buf + kJDWPHeaderSizeOffset, expandBufGetLength(pReply)); + JDWP::Set4BE(buf + kJDWPHeaderIdOffset, request_id); + JDWP::Set1(buf + kJDWPHeaderFlagsOffset, kJDWPFlagReply); // flags + JDWP::Set2BE(buf + kJDWPHeaderErrorCodeOffset, JDWP::ERR_NONE); +} + +void Dbg::FinishInvokeMethod(DebugInvokeReq* pReq) { + CHECK_NE(Thread::Current(), GetDebugThread()) << "This must be called by the event thread"; + + JDWP::ExpandBuf* const pReply = pReq->reply; + CHECK(pReply != nullptr) << "No reply attached to DebugInvokeReq"; + + // We need to prevent other threads (including JDWP thread) from interacting with the debugger + // while we send the reply but are not yet suspended. The JDWP token will be released just before + // we suspend ourself again (see ThreadList::SuspendSelfForDebugger). + gJdwpState->AcquireJdwpTokenForEvent(pReq->thread_id); + + // Send the reply unless the debugger detached before the completion of the method. + if (IsDebuggerActive()) { + const size_t replyDataLength = expandBufGetLength(pReply) - kJDWPHeaderLen; + VLOG(jdwp) << StringPrintf("REPLY INVOKE id=0x%06x (length=%zu)", + pReq->request_id, replyDataLength); + + gJdwpState->SendRequest(pReply); + } else { + VLOG(jdwp) << "Not sending invoke reply because debugger detached"; } } diff --git a/runtime/debugger.h b/runtime/debugger.h index 3b92d361f2..fd7d46c37e 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -42,6 +42,7 @@ class Throwable; class ArtField; class ArtMethod; class ObjectRegistry; +class ScopedObjectAccess; class ScopedObjectAccessUnchecked; class StackVisitor; class Thread; @@ -50,33 +51,32 @@ class Thread; * Invoke-during-breakpoint support. */ struct DebugInvokeReq { - DebugInvokeReq(mirror::Object* invoke_receiver, mirror::Class* invoke_class, + DebugInvokeReq(uint32_t invoke_request_id, JDWP::ObjectId invoke_thread_id, + mirror::Object* invoke_receiver, mirror::Class* invoke_class, ArtMethod* invoke_method, uint32_t invoke_options, - uint64_t* args, uint32_t args_count) - : receiver(invoke_receiver), klass(invoke_class), method(invoke_method), - arg_count(args_count), arg_values(args), options(invoke_options), - error(JDWP::ERR_NONE), result_tag(JDWP::JT_VOID), result_value(0), exception(0), - lock("a DebugInvokeReq lock", kBreakpointInvokeLock), - cond("a DebugInvokeReq condition variable", lock) { + uint64_t args[], uint32_t args_count) + : request_id(invoke_request_id), thread_id(invoke_thread_id), receiver(invoke_receiver), + klass(invoke_class), method(invoke_method), arg_count(args_count), arg_values(args), + options(invoke_options), reply(JDWP::expandBufAlloc()) { } - /* request */ - GcRoot<mirror::Object> receiver; // not used for ClassType.InvokeMethod + ~DebugInvokeReq() { + JDWP::expandBufFree(reply); + } + + // Request + const uint32_t request_id; + const JDWP::ObjectId thread_id; + GcRoot<mirror::Object> receiver; // not used for ClassType.InvokeMethod. GcRoot<mirror::Class> klass; - ArtMethod* method; + ArtMethod* const method; const uint32_t arg_count; - uint64_t* const arg_values; // will be null if arg_count_ == 0 + std::unique_ptr<uint64_t[]> arg_values; // will be null if arg_count_ == 0. We take ownership + // of this array so we must delete it upon destruction. const uint32_t options; - /* result */ - JDWP::JdwpError error; - JDWP::JdwpTag result_tag; - uint64_t result_value; // either a primitive value or an ObjectId - JDWP::ObjectId exception; - - /* condition variable to wait on while the method executes */ - Mutex lock DEFAULT_MUTEX_ACQUIRED_AFTER; - ConditionVariable cond GUARDED_BY(lock); + // Reply + JDWP::ExpandBuf* const reply; void VisitRoots(RootVisitor* visitor, const RootInfo& root_info) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -605,19 +605,39 @@ class Dbg { LOCKS_EXCLUDED(Locks::thread_list_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - // Invoke support for commands ClassType.InvokeMethod, ClassType.NewInstance and - // ObjectReference.InvokeMethod. - static JDWP::JdwpError InvokeMethod(JDWP::ObjectId thread_id, JDWP::ObjectId object_id, - JDWP::RefTypeId class_id, JDWP::MethodId method_id, - uint32_t arg_count, uint64_t* arg_values, - JDWP::JdwpTag* arg_types, uint32_t options, - JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, - JDWP::ObjectId* pExceptObj) + /* + * Invoke support + */ + + // Called by the JDWP thread to prepare invocation in the event thread (suspended on an event). + // If the information sent by the debugger is incorrect, it will send a reply with the + // appropriate error code. Otherwise, it will attach a DebugInvokeReq object to the event thread + // and resume it (and possibly other threads depending on the invoke options). + // Unlike other commands, the JDWP thread will not send the reply to the debugger (see + // JdwpState::ProcessRequest). The reply will be sent by the event thread itself after method + // invocation completes (see FinishInvokeMethod). This is required to allow the JDWP thread to + // process incoming commands from the debugger while the invocation is still in progress in the + // event thread, especially if it gets suspended by a debug event occurring in another thread. + static JDWP::JdwpError PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thread_id, + JDWP::ObjectId object_id, JDWP::RefTypeId class_id, + JDWP::MethodId method_id, uint32_t arg_count, + uint64_t arg_values[], JDWP::JdwpTag* arg_types, + uint32_t options) LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::thread_suspend_count_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + // Called by the event thread to execute a method prepared by the JDWP thread in the given + // DebugInvokeReq object. Once the invocation completes, the event thread attaches a reply + // to that DebugInvokeReq object so it can be sent to the debugger only when the event thread + // is ready to suspend (see FinishInvokeMethod). static void ExecuteMethod(DebugInvokeReq* pReq); + // Called by the event thread to send the reply of the invoke (created in ExecuteMethod) + // before suspending itself. This is to ensure the thread is ready to suspend before the + // debugger receives the reply. + static void FinishInvokeMethod(DebugInvokeReq* pReq); + /* * DDM support. */ @@ -694,6 +714,14 @@ class Dbg { } private: + static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + static void BuildInvokeReply(JDWP::ExpandBuf* pReply, uint32_t request_id, + JDWP::JdwpTag result_tag, uint64_t result_value, + JDWP::ObjectId exception) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + static JDWP::JdwpError GetLocalValue(const StackVisitor& visitor, ScopedObjectAccessUnchecked& soa, int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t width) diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h index e18d10fa0a..7c48985dfe 100644 --- a/runtime/jdwp/jdwp.h +++ b/runtime/jdwp/jdwp.h @@ -297,7 +297,7 @@ struct JdwpState { private: explicit JdwpState(const JdwpOptions* options); - size_t ProcessRequest(Request* request, ExpandBuf* pReply); + size_t ProcessRequest(Request* request, ExpandBuf* pReply, bool* skip_reply); bool InvokeInProgress(); bool IsConnected(); void SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id) diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc index 612af8bc99..14f097f72a 100644 --- a/runtime/jdwp/jdwp_event.cc +++ b/runtime/jdwp/jdwp_event.cc @@ -99,10 +99,6 @@ put ourselves to sleep. That way we don't interfere with anyone else and don't allow anyone else to interfere with us. */ - -#define kJdwpEventCommandSet 64 -#define kJdwpCompositeCommand 100 - namespace art { namespace JDWP { @@ -612,13 +608,10 @@ void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId */ DebugInvokeReq* const pReq = Dbg::GetInvokeReq(); if (pReq == nullptr) { - /*LOGD("SuspendByPolicy: no invoke needed");*/ break; } - /* grab this before posting/suspending again */ - AcquireJdwpTokenForEvent(thread_self_id); - + // Execute method. Dbg::ExecuteMethod(pReq); } } @@ -749,11 +742,11 @@ static ExpandBuf* eventPrep() { void JdwpState::EventFinish(ExpandBuf* pReq) { uint8_t* buf = expandBufGetBuffer(pReq); - Set4BE(buf, expandBufGetLength(pReq)); - Set4BE(buf + 4, NextRequestSerial()); - Set1(buf + 8, 0); /* flags */ - Set1(buf + 9, kJdwpEventCommandSet); - Set1(buf + 10, kJdwpCompositeCommand); + Set4BE(buf + kJDWPHeaderSizeOffset, expandBufGetLength(pReq)); + Set4BE(buf + kJDWPHeaderIdOffset, NextRequestSerial()); + Set1(buf + kJDWPHeaderFlagsOffset, 0); /* flags */ + Set1(buf + kJDWPHeaderCmdSetOffset, kJDWPEventCmdSet); + Set1(buf + kJDWPHeaderCmdOffset, kJDWPEventCompositeCmd); SendRequest(pReq); diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc index f7f70f6ed7..d4e2656b7e 100644 --- a/runtime/jdwp/jdwp_handler.cc +++ b/runtime/jdwp/jdwp_handler.cc @@ -52,17 +52,6 @@ std::string DescribeRefTypeId(const RefTypeId& ref_type_id) { return StringPrintf("%#" PRIx64 " (%s)", ref_type_id, signature.c_str()); } -// Helper function: write a variable-width value into the output input buffer. -static void WriteValue(ExpandBuf* pReply, int width, uint64_t value) { - switch (width) { - case 1: expandBufAdd1(pReply, value); break; - case 2: expandBufAdd2BE(pReply, value); break; - case 4: expandBufAdd4BE(pReply, value); break; - case 8: expandBufAdd8BE(pReply, value); break; - default: LOG(FATAL) << width; break; - } -} - static JdwpError WriteTaggedObject(ExpandBuf* reply, ObjectId object_id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { uint8_t tag; @@ -92,7 +81,7 @@ static JdwpError WriteTaggedObjectList(ExpandBuf* reply, const std::vector<Objec * If "is_constructor" is set, this returns "object_id" rather than the * expected-to-be-void return value of the called function. */ -static JdwpError RequestInvoke(JdwpState*, Request* request, ExpandBuf* pReply, +static JdwpError RequestInvoke(JdwpState*, Request* request, ObjectId thread_id, ObjectId object_id, RefTypeId class_id, MethodId method_id, bool is_constructor) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { @@ -122,49 +111,15 @@ static JdwpError RequestInvoke(JdwpState*, Request* request, ExpandBuf* pReply, (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "", (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : ""); - JdwpTag resultTag; - uint64_t resultValue; - ObjectId exceptObjId; - JdwpError err = Dbg::InvokeMethod(thread_id, object_id, class_id, method_id, arg_count, - argValues.get(), argTypes.get(), options, &resultTag, - &resultValue, &exceptObjId); - if (err != ERR_NONE) { - return err; - } - - if (is_constructor) { - // If we invoked a constructor (which actually returns void), return the receiver, - // unless we threw, in which case we return null. - resultTag = JT_OBJECT; - resultValue = (exceptObjId == 0) ? object_id : 0; - } - - size_t width = Dbg::GetTagWidth(resultTag); - expandBufAdd1(pReply, resultTag); - if (width != 0) { - WriteValue(pReply, width, resultValue); - } - expandBufAdd1(pReply, JT_OBJECT); - expandBufAddObjectId(pReply, exceptObjId); - - VLOG(jdwp) << " --> returned " << resultTag - << StringPrintf(" %#" PRIx64 " (except=%#" PRIx64 ")", resultValue, exceptObjId); - - /* show detailed debug output */ - if (resultTag == JT_STRING && exceptObjId == 0) { - if (resultValue != 0) { - if (VLOG_IS_ON(jdwp)) { - std::string result_string; - JDWP::JdwpError error = Dbg::StringToUtf8(resultValue, &result_string); - CHECK_EQ(error, JDWP::ERR_NONE); - VLOG(jdwp) << " string '" << result_string << "'"; - } - } else { - VLOG(jdwp) << " string (null)"; - } + JDWP::JdwpError error = Dbg::PrepareInvokeMethod(request->GetId(), thread_id, object_id, + class_id, method_id, arg_count, + argValues.get(), argTypes.get(), options); + if (error == JDWP::ERR_NONE) { + // We successfully requested the invoke. The event thread now owns the arguments array in its + // DebugInvokeReq mailbox. + argValues.release(); } - - return err; + return error; } static JdwpError VM_Version(JdwpState*, Request*, ExpandBuf* pReply) @@ -684,13 +639,14 @@ static JdwpError CT_SetValues(JdwpState* , Request* request, ExpandBuf*) * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on * values in the "variables" display. */ -static JdwpError CT_InvokeMethod(JdwpState* state, Request* request, ExpandBuf* pReply) +static JdwpError CT_InvokeMethod(JdwpState* state, Request* request, + ExpandBuf* pReply ATTRIBUTE_UNUSED) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { RefTypeId class_id = request->ReadRefTypeId(); ObjectId thread_id = request->ReadThreadId(); MethodId method_id = request->ReadMethodId(); - return RequestInvoke(state, request, pReply, thread_id, 0, class_id, method_id, false); + return RequestInvoke(state, request, thread_id, 0, class_id, method_id, false); } /* @@ -700,7 +656,8 @@ static JdwpError CT_InvokeMethod(JdwpState* state, Request* request, ExpandBuf* * Example: in IntelliJ, create a watch on "new String(myByteArray)" to * see the contents of a byte[] as a string. */ -static JdwpError CT_NewInstance(JdwpState* state, Request* request, ExpandBuf* pReply) +static JdwpError CT_NewInstance(JdwpState* state, Request* request, + ExpandBuf* pReply ATTRIBUTE_UNUSED) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { RefTypeId class_id = request->ReadRefTypeId(); ObjectId thread_id = request->ReadThreadId(); @@ -711,7 +668,7 @@ static JdwpError CT_NewInstance(JdwpState* state, Request* request, ExpandBuf* p if (status != ERR_NONE) { return status; } - return RequestInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, true); + return RequestInvoke(state, request, thread_id, object_id, class_id, method_id, true); } /* @@ -863,14 +820,15 @@ static JdwpError OR_MonitorInfo(JdwpState*, Request* request, ExpandBuf* reply) * object), it will try to invoke the object's toString() function. This * feature becomes crucial when examining ArrayLists with Eclipse. */ -static JdwpError OR_InvokeMethod(JdwpState* state, Request* request, ExpandBuf* pReply) +static JdwpError OR_InvokeMethod(JdwpState* state, Request* request, + ExpandBuf* pReply ATTRIBUTE_UNUSED) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { ObjectId object_id = request->ReadObjectId(); ObjectId thread_id = request->ReadThreadId(); RefTypeId class_id = request->ReadRefTypeId(); MethodId method_id = request->ReadMethodId(); - return RequestInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, false); + return RequestInvoke(state, request, thread_id, object_id, class_id, method_id, false); } static JdwpError OR_DisableCollection(JdwpState*, Request* request, ExpandBuf*) @@ -1602,13 +1560,27 @@ static std::string DescribeCommand(Request* request) { return result; } +// Returns true if the given command_set and command identify an "invoke" command. +static bool IsInvokeCommand(uint8_t command_set, uint8_t command) { + if (command_set == kJDWPClassTypeCmdSet) { + return command == kJDWPClassTypeInvokeMethodCmd || command == kJDWPClassTypeNewInstanceCmd; + } else if (command_set == kJDWPObjectReferenceCmdSet) { + return command == kJDWPObjectReferenceInvokeCmd; + } else { + return false; + } +} + /* - * Process a request from the debugger. + * Process a request from the debugger. The skip_reply flag is set to true to indicate to the + * caller the reply must not be sent to the debugger. This is used for invoke commands where the + * reply is sent by the event thread after completing the invoke. * * On entry, the JDWP thread is in VMWAIT. */ -size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) { +size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply, bool* skip_reply) { JdwpError result = ERR_NONE; + *skip_reply = false; if (request->GetCommandSet() != kJDWPDdmCmdSet) { /* @@ -1661,24 +1633,31 @@ size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) { result = ERR_NOT_IMPLEMENTED; } - /* - * Set up the reply header. - * - * If we encountered an error, only send the header back. - */ - uint8_t* replyBuf = expandBufGetBuffer(pReply); - size_t replyLength = (result == ERR_NONE) ? expandBufGetLength(pReply) : kJDWPHeaderLen; - Set4BE(replyBuf + 0, replyLength); - Set4BE(replyBuf + 4, request->GetId()); - Set1(replyBuf + 8, kJDWPFlagReply); - Set2BE(replyBuf + 9, result); - - CHECK_GT(expandBufGetLength(pReply), 0U) << GetCommandName(request) << " " << request->GetId(); - - size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen; - VLOG(jdwp) << "REPLY: " << GetCommandName(request) << " " << result << " (length=" << respLen << ")"; - if (false) { - VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen, false, ""); + size_t replyLength = 0U; + if (result == ERR_NONE && IsInvokeCommand(request->GetCommandSet(), request->GetCommand())) { + // We successfully request an invoke in the event thread. It will send the reply once the + // invoke completes so we must not send it now. + *skip_reply = true; + } else { + /* + * Set up the reply header. + * + * If we encountered an error, only send the header back. + */ + uint8_t* replyBuf = expandBufGetBuffer(pReply); + replyLength = (result == ERR_NONE) ? expandBufGetLength(pReply) : kJDWPHeaderLen; + Set4BE(replyBuf + kJDWPHeaderSizeOffset, replyLength); + Set4BE(replyBuf + kJDWPHeaderIdOffset, request->GetId()); + Set1(replyBuf + kJDWPHeaderFlagsOffset, kJDWPFlagReply); + Set2BE(replyBuf + kJDWPHeaderErrorCodeOffset, result); + + CHECK_GT(expandBufGetLength(pReply), 0U) << GetCommandName(request) << " " << request->GetId(); + + size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen; + VLOG(jdwp) << "REPLY: " << GetCommandName(request) << " " << result << " (length=" << respLen << ")"; + if (false) { + VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen, false, ""); + } } VLOG(jdwp) << "----------"; diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc index e6b97a2083..6bc5e27f85 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -395,8 +395,15 @@ bool JdwpState::HandlePacket() { JDWP::Request request(netStateBase->input_buffer_, netStateBase->input_count_); ExpandBuf* pReply = expandBufAlloc(); - size_t replyLength = ProcessRequest(&request, pReply); - ssize_t cc = netStateBase->WritePacket(pReply, replyLength); + bool skip_reply = false; + size_t replyLength = ProcessRequest(&request, pReply, &skip_reply); + ssize_t cc = 0; + if (!skip_reply) { + cc = netStateBase->WritePacket(pReply, replyLength); + } else { + DCHECK_EQ(replyLength, 0U); + } + expandBufFree(pReply); /* * We processed this request and sent its reply so we can release the JDWP token. @@ -405,10 +412,8 @@ bool JdwpState::HandlePacket() { if (cc != static_cast<ssize_t>(replyLength)) { PLOG(ERROR) << "Failed sending reply to debugger"; - expandBufFree(pReply); return false; } - expandBufFree(pReply); netStateBase->ConsumeBytes(request.GetLength()); { MutexLock mu(self, shutdown_lock_); diff --git a/runtime/jdwp/jdwp_priv.h b/runtime/jdwp/jdwp_priv.h index f290be0f52..d58467d108 100644 --- a/runtime/jdwp/jdwp_priv.h +++ b/runtime/jdwp/jdwp_priv.h @@ -29,15 +29,32 @@ /* * JDWP constants. */ -#define kJDWPHeaderLen 11 -#define kJDWPFlagReply 0x80 - -#define kMagicHandshake "JDWP-Handshake" -#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1) +static constexpr size_t kJDWPHeaderSizeOffset = 0U; +static constexpr size_t kJDWPHeaderIdOffset = 4U; +static constexpr size_t kJDWPHeaderFlagsOffset = 8U; +static constexpr size_t kJDWPHeaderErrorCodeOffset = 9U; +static constexpr size_t kJDWPHeaderCmdSetOffset = 9U; +static constexpr size_t kJDWPHeaderCmdOffset = 10U; +static constexpr size_t kJDWPHeaderLen = 11U; +static constexpr uint8_t kJDWPFlagReply = 0x80; + +static constexpr const char kMagicHandshake[] = "JDWP-Handshake"; +static constexpr size_t kMagicHandshakeLen = sizeof(kMagicHandshake) - 1; + +/* Invoke commands */ +static constexpr uint8_t kJDWPClassTypeCmdSet = 3U; +static constexpr uint8_t kJDWPClassTypeInvokeMethodCmd = 3U; +static constexpr uint8_t kJDWPClassTypeNewInstanceCmd = 4U; +static constexpr uint8_t kJDWPObjectReferenceCmdSet = 9U; +static constexpr uint8_t kJDWPObjectReferenceInvokeCmd = 6U; + +/* Event command */ +static constexpr uint8_t kJDWPEventCmdSet = 64U; +static constexpr uint8_t kJDWPEventCompositeCmd = 100U; /* DDM support */ -#define kJDWPDdmCmdSet 199 /* 0xc7, or 'G'+128 */ -#define kJDWPDdmCmd 1 +static constexpr uint8_t kJDWPDdmCmdSet = 199U; // 0xc7, or 'G'+128 +static constexpr uint8_t kJDWPDdmCmd = 1U; namespace art { diff --git a/runtime/thread.cc b/runtime/thread.cc index fe98b0a98a..fe8b0d8c60 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2578,12 +2578,11 @@ void Thread::SetDebugInvokeReq(DebugInvokeReq* req) { } void Thread::ClearDebugInvokeReq() { - CHECK(Dbg::IsDebuggerActive()); CHECK(GetInvokeReq() != nullptr) << "Debug invoke req not active in thread " << *this; CHECK(Thread::Current() == this) << "Debug invoke must be finished by the thread itself"; - // We do not own the DebugInvokeReq* so we must not delete it, it is the responsibility of - // the owner (the JDWP thread). + DebugInvokeReq* req = tlsPtr_.debug_invoke_req; tlsPtr_.debug_invoke_req = nullptr; + delete req; } void Thread::PushVerifier(verifier::MethodVerifier* verifier) { diff --git a/runtime/thread.h b/runtime/thread.h index 9311bef48a..0e71c08b07 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -781,15 +781,14 @@ class Thread { void DeactivateSingleStepControl(); // Sets debug invoke request for debugging. When the thread is resumed, - // it executes the method described by this request then suspends itself. - // The thread does not take ownership of the given DebugInvokeReq*, it is - // owned by the JDWP thread which is waiting for the execution of the - // method. + // it executes the method described by this request then sends the reply + // before suspending itself. The thread takes the ownership of the given + // DebugInvokeReq*. It is deleted by a call to ClearDebugInvokeReq. void SetDebugInvokeReq(DebugInvokeReq* req); // Clears debug invoke request for debugging. When the thread completes - // method invocation, it clears its debug invoke request, signals the - // JDWP thread and suspends itself. + // method invocation, it deletes its debug invoke request and suspends + // itself. void ClearDebugInvokeReq(); // Returns the fake exception used to activate deoptimization. diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index af9ba6848b..b697b43a77 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -875,31 +875,36 @@ void ThreadList::SuspendSelfForDebugger() { // The debugger thread must not suspend itself due to debugger activity! Thread* debug_thread = Dbg::GetDebugThread(); - CHECK(debug_thread != nullptr); CHECK(self != debug_thread); CHECK_NE(self->GetState(), kRunnable); Locks::mutator_lock_->AssertNotHeld(self); - { + // The debugger may have detached while we were executing an invoke request. In that case, we + // must not suspend ourself. + DebugInvokeReq* pReq = self->GetInvokeReq(); + const bool skip_thread_suspension = (pReq != nullptr && !Dbg::IsDebuggerActive()); + if (!skip_thread_suspension) { // Collisions with other suspends aren't really interesting. We want // to ensure that we're the only one fiddling with the suspend count // though. MutexLock mu(self, *Locks::thread_suspend_count_lock_); self->ModifySuspendCount(self, +1, true); CHECK_GT(self->GetSuspendCount(), 0); - } - VLOG(threads) << *self << " self-suspending (debugger)"; + VLOG(threads) << *self << " self-suspending (debugger)"; + } else { + // We must no longer be subject to debugger suspension. + MutexLock mu(self, *Locks::thread_suspend_count_lock_); + CHECK_EQ(self->GetDebugSuspendCount(), 0) << "Debugger detached without resuming us"; - // Tell JDWP we've completed invocation and are ready to suspend. - DebugInvokeReq* const pReq = self->GetInvokeReq(); + VLOG(threads) << *self << " not self-suspending because debugger detached during invoke"; + } + + // If the debugger requested an invoke, we need to send the reply and clear the request. if (pReq != nullptr) { - // Clear debug invoke request before signaling. + Dbg::FinishInvokeMethod(pReq); self->ClearDebugInvokeReq(); - - VLOG(jdwp) << "invoke complete, signaling"; - MutexLock mu(self, pReq->lock); - pReq->cond.Signal(self); + pReq = nullptr; // object has been deleted, clear it for safety. } // Tell JDWP that we've completed suspension. The JDWP thread can't diff --git a/test/482-checker-loop-back-edge-use/src/Main.java b/test/482-checker-loop-back-edge-use/src/Main.java index 5754723d8a..a4280de749 100644 --- a/test/482-checker-loop-back-edge-use/src/Main.java +++ b/test/482-checker-loop-back-edge-use/src/Main.java @@ -36,8 +36,8 @@ public class Main { } /// CHECK-START: void Main.loop3(boolean) liveness (after) - /// CHECK: ParameterValue liveness:4 ranges:{[4,62)} uses:[58,62] - /// CHECK: Goto liveness:60 + /// CHECK: ParameterValue liveness:4 ranges:{[4,64)} uses:[60,64] + /// CHECK: Goto liveness:62 /// CHECK-START: void Main.loop3(boolean) liveness (after) /// CHECK-NOT: Goto liveness:56 @@ -63,9 +63,9 @@ public class Main { } /// CHECK-START: void Main.loop5(boolean) liveness (after) - /// CHECK: ParameterValue liveness:4 ranges:{[4,52)} uses:[35,44,48,52] - /// CHECK: Goto liveness:46 - /// CHECK: Goto liveness:50 + /// CHECK: ParameterValue liveness:4 ranges:{[4,54)} uses:[37,46,50,54] + /// CHECK: Goto liveness:48 + /// CHECK: Goto liveness:52 public static void loop5(boolean incoming) { // 'incoming' must have a use at both back edges. while (Runtime.getRuntime() != null) { @@ -76,8 +76,8 @@ public class Main { } /// CHECK-START: void Main.loop6(boolean) liveness (after) - /// CHECK: ParameterValue liveness:4 ranges:{[4,48)} uses:[26,48] - /// CHECK: Goto liveness:46 + /// CHECK: ParameterValue liveness:4 ranges:{[4,50)} uses:[26,50] + /// CHECK: Goto liveness:48 /// CHECK-START: void Main.loop6(boolean) liveness (after) /// CHECK-NOT: Goto liveness:24 @@ -90,9 +90,9 @@ public class Main { } /// CHECK-START: void Main.loop7(boolean) liveness (after) - /// CHECK: ParameterValue liveness:4 ranges:{[4,52)} uses:[34,43,48,52] - /// CHECK: Goto liveness:46 - /// CHECK: Goto liveness:50 + /// CHECK: ParameterValue liveness:4 ranges:{[4,54)} uses:[36,45,50,54] + /// CHECK: Goto liveness:48 + /// CHECK: Goto liveness:52 public static void loop7(boolean incoming) { // 'incoming' must have a use at both back edges. while (Runtime.getRuntime() != null) { @@ -102,9 +102,9 @@ public class Main { } /// CHECK-START: void Main.loop8() liveness (after) - /// CHECK: StaticFieldGet liveness:14 ranges:{[14,46)} uses:[37,42,46] - /// CHECK: Goto liveness:40 - /// CHECK: Goto liveness:44 + /// CHECK: StaticFieldGet liveness:14 ranges:{[14,48)} uses:[39,44,48] + /// CHECK: Goto liveness:42 + /// CHECK: Goto liveness:46 public static void loop8() { // 'incoming' must have a use at both back edges. boolean incoming = field; @@ -114,8 +114,8 @@ public class Main { } /// CHECK-START: void Main.loop9() liveness (after) - /// CHECK: StaticFieldGet liveness:24 ranges:{[24,38)} uses:[33,38] - /// CHECK: Goto liveness:40 + /// CHECK: StaticFieldGet liveness:26 ranges:{[26,40)} uses:[35,40] + /// CHECK: Goto liveness:42 public static void loop9() { while (Runtime.getRuntime() != null) { // 'incoming' must only have a use in the inner loop. diff --git a/test/496-checker-inlining-and-class-loader/expected.txt b/test/496-checker-inlining-and-class-loader/expected.txt new file mode 100644 index 0000000000..c6fcb51ecf --- /dev/null +++ b/test/496-checker-inlining-and-class-loader/expected.txt @@ -0,0 +1,4 @@ +Request for LoadedByMyClassLoader +Request for Main +In between the two calls. +In $noinline$bar diff --git a/test/496-checker-inlining-and-class-loader/info.txt b/test/496-checker-inlining-and-class-loader/info.txt new file mode 100644 index 0000000000..aa4b256207 --- /dev/null +++ b/test/496-checker-inlining-and-class-loader/info.txt @@ -0,0 +1,2 @@ +Regression test to ensure compilers preserve JLS +semantics of class loading. diff --git a/test/496-checker-inlining-and-class-loader/src/Main.java b/test/496-checker-inlining-and-class-loader/src/Main.java new file mode 100644 index 0000000000..f6d0b41a58 --- /dev/null +++ b/test/496-checker-inlining-and-class-loader/src/Main.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +class MyClassLoader extends ClassLoader { + MyClassLoader() throws Exception { + super(MyClassLoader.class.getClassLoader()); + + // Some magic to get access to the pathList field of BaseDexClassLoader. + ClassLoader loader = getClass().getClassLoader(); + Class<?> baseDexClassLoader = loader.getClass().getSuperclass(); + Field f = baseDexClassLoader.getDeclaredField("pathList"); + f.setAccessible(true); + Object pathList = f.get(loader); + + // Some magic to get access to the dexField field of pathList. + f = pathList.getClass().getDeclaredField("dexElements"); + f.setAccessible(true); + dexElements = (Object[]) f.get(pathList); + dexFileField = dexElements[0].getClass().getDeclaredField("dexFile"); + dexFileField.setAccessible(true); + } + + Object[] dexElements; + Field dexFileField; + + protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { + System.out.println("Request for " + className); + + // We're only going to handle LoadedByMyClassLoader. + if (className != "LoadedByMyClassLoader") { + return getParent().loadClass(className); + } + + // Mimic what DexPathList.findClass is doing. + try { + for (Object element : dexElements) { + Object dex = dexFileField.get(element); + Method method = dex.getClass().getDeclaredMethod( + "loadClassBinaryName", String.class, ClassLoader.class, List.class); + + if (dex != null) { + Class clazz = (Class)method.invoke(dex, className, this, null); + if (clazz != null) { + return clazz; + } + } + } + } catch (Exception e) { /* Ignore */ } + return null; + } +} + +class LoadedByMyClassLoader { + /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (before) + /// CHECK: LoadClass + /// CHECK-NEXT: ClinitCheck + /// CHECK-NEXT: InvokeStaticOrDirect + /// CHECK-NEXT: LoadClass + /// CHECK-NEXT: ClinitCheck + /// CHECK-NEXT: StaticFieldGet + /// CHECK-NEXT: LoadString + /// CHECK-NEXT: NullCheck + /// CHECK-NEXT: InvokeVirtual + + /// CHECK-START: void LoadedByMyClassLoader.bar() inliner (after) + /// CHECK: LoadClass + /// CHECK-NEXT: ClinitCheck + /* We inlined Main.$inline$bar */ + /// CHECK-NEXT: LoadClass + /// CHECK-NEXT: ClinitCheck + /// CHECK-NEXT: StaticFieldGet + /// CHECK-NEXT: LoadString + /// CHECK-NEXT: NullCheck + /// CHECK-NEXT: InvokeVirtual + + /// CHECK-START: void LoadedByMyClassLoader.bar() register (before) + /* Load and initialize Main */ + /// CHECK: LoadClass gen_clinit_check:true + /* Load and initialize System */ + /// CHECK-NEXT: LoadClass gen_clinit_check:true + /// CHECK-NEXT: StaticFieldGet + /// CHECK-NEXT: LoadString + /// CHECK-NEXT: NullCheck + /// CHECK-NEXT: InvokeVirtual + public static void bar() { + Main.$inline$bar(); + System.out.println("In between the two calls."); + Main.$noinline$bar(); + } +} + +class Main { + public static void main(String[] args) throws Exception { + MyClassLoader o = new MyClassLoader(); + Class foo = o.loadClass("LoadedByMyClassLoader"); + Method m = foo.getDeclaredMethod("bar"); + m.invoke(null); + } + + public static void $inline$bar() { + } + + public static void $noinline$bar() { + try { + System.out.println("In $noinline$bar"); + } catch (Throwable t) { /* Ignore */ } + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index b948e4b1b0..82a62953d5 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -376,7 +376,8 @@ TEST_ART_BROKEN_JIT_RUN_TESTS := # Known broken tests for the default compiler (Quick). TEST_ART_BROKEN_DEFAULT_RUN_TESTS := \ - 457-regs + 457-regs \ + 496-checker-inlining-and-class-loader ifneq (,$(filter default,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh index b84ae473f0..da7131c15d 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -19,7 +19,7 @@ if [ ! -d art ]; then exit 1 fi -common_targets="vogar vogar.jar core-tests apache-harmony-jdwp-tests-hostdex out/host/linux-x86/bin/adb jsr166-tests libjavacoretests" +common_targets="vogar vogar.jar core-tests apache-harmony-jdwp-tests-hostdex out/host/linux-x86/bin/adb jsr166-tests conscrypt-tests" android_root="/data/local/tmp/system" linker="linker" mode="target" @@ -60,7 +60,7 @@ while true; do done if [[ $mode == "host" ]]; then - make_command="make $j_arg build-art-host-tests $common_targets" + make_command="make $j_arg build-art-host-tests $common_targets out/host/linux-x86/lib/libjavacoretests.so out/host/linux-x86/lib64/libjavacoretests.so" echo "Executing $make_command" $make_command elif [[ $mode == "target" ]]; then @@ -70,7 +70,7 @@ elif [[ $mode == "target" ]]; then # Use '-e' to force the override of TARGET_GLOBAL_LDFLAGS. # Also, we build extra tools that will be used by tests, so that # they are compiled with our own linker. - make_command="make -e $j_arg build-art-target-tests $common_targets libjavacrypto linker toybox toolbox sh" + make_command="make -e $j_arg build-art-target-tests $common_targets libjavacrypto libjavacoretests linker toybox toolbox sh" echo "Executing env $env $make_command" env $env $make_command fi diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt index b053f0d844..5d58c29816 100644 --- a/tools/libcore_failures.txt +++ b/tools/libcore_failures.txt @@ -132,5 +132,42 @@ result: EXEC_FAILED, names: ["libcore.javax.crypto.CipherTest#testCipher_ShortBlock_Failure", "libcore.javax.crypto.CipherTest#testCipher_Success"] +}, +{ + description: "Tests newly added to the build bot (b/21722374)", + result: EXEC_FAILED, + names: ["com.android.org.conscrypt.NativeCryptoTest#test_SSL_do_handshake_clientCertificateRequested_throws_after_renegotiate", + "com.android.org.conscrypt.NativeCryptoTest#test_SSL_new", + "com.android.org.conscrypt.NativeCryptoTest#test_SSL_renegotiate", + "com.android.org.conscrypt.NativeCryptoTest#test_SSL_set_session_creation_enabled", + "libcore.java.security.KeyPairGeneratorTest#test_getInstance_provider1", + "libcore.java.security.KeyStoreTest", + "libcore.java.security.ProviderTest#test_Provider_getServices", + "libcore.java.security.SignatureTest#test18566_AlgorithmOid_MissingNull_Failure", + "libcore.java.security.SignatureTest#testVerify_NONEwithRSA_Key_SignatureTooLarge_Failure", + "libcore.java.security.SignatureTest#testVerify_SHA1withRSA_Key_WrongExpectedSignature_Failure", + "libcore.java.security.SignatureTest#test_getInstance", + "libcore.javax.net.ssl.KeyManagerFactoryTest#test_KeyManagerFactory_getDefaultAlgorithm", + "libcore.javax.net.ssl.KeyManagerFactoryTest#test_KeyManagerFactory_getInstance", + "libcore.javax.net.ssl.SSLContextTest#test_SSLContext_defaultConfiguration", + "libcore.javax.net.ssl.SSLSocketTest#test_SSLSocket_NoEnabledCipherSuites_Failure", + "libcore.javax.net.ssl.SSLSocketTest#test_SSLSocket_sendsTlsFallbackScsv_InappropriateFallback_Failure"] +}, +{ + description: "Tests newly added to the build bot (b/21722374). Not failing with adb sync; ./run-libcore-tests.sh", + result: EXEC_FAILED, + names: ["com.android.org.conscrypt.NativeCryptoTest#test_SSL_set_cipher_lists", + "libcore.java.security.KeyStoreTest#test_KeyStore_load_InputStream", + "libcore.java.security.KeyStoreTest#test_KeyStore_load_LoadStoreParameter", + "libcore.java.security.KeyStoreTest#test_KeyStore_size", + "libcore.java.security.MessageDigestTest#test_getInstance", + "libcore.java.security.cert.X509CertificateTest#test_Provider", + "libcore.javax.net.ssl.TrustManagerFactoryTest#test_TrustManagerFactory_getDefaultAlgorithm", + "libcore.javax.net.ssl.TrustManagerFactoryTest#test_TrustManagerFactory_getInstance"] +}, +{ + description: "Test failing because of wrong dates in build bot devices", + result: EXEC_FAILED, + names: ["libcore.java.security.cert.X509CertificateNistPkitsTest"] } ] diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 4e76eb4354..03016facb2 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -22,6 +22,9 @@ fi # Jar containing jsr166 tests. jsr166_test_jar=out/target/common/obj/JAVA_LIBRARIES/jsr166-tests_intermediates/javalib.jar +# Jar containing conscrypt tests. +conscrypt_test_jar=out/target/common/obj/JAVA_LIBRARIES/conscrypt-tests_intermediates/javalib.jar + # Jar containing all the other tests. test_jar=out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/javalib.jar @@ -33,14 +36,17 @@ if [ ! -f $test_jar ]; then fi # Packages that currently work correctly with the expectation files. -working_packages=("dalvik.system" +working_packages=("com.android.org.conscrypt" + "dalvik.system" "libcore.icu" "libcore.io" "libcore.java.lang" "libcore.java.math" + "libcore.java.security" "libcore.java.text" "libcore.java.util" "libcore.javax.crypto" + "libcore.javax.net" "libcore.javax.security" "libcore.javax.sql" "libcore.javax.xml" @@ -66,4 +72,4 @@ working_packages=("dalvik.system" # Run the tests using vogar. echo "Running tests for the following test packages:" echo ${working_packages[@]} | tr " " "\n" -vogar $@ --expectations art/tools/libcore_failures.txt --classpath $jsr166_test_jar --classpath $test_jar ${working_packages[@]} +vogar $@ --expectations art/tools/libcore_failures.txt --classpath $conscrypt_test_jar --classpath $jsr166_test_jar --classpath $test_jar ${working_packages[@]} |