aoqi@0: /* aoqi@0: * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: * aoqi@0: */ aoqi@0: aoqi@0: #include "precompiled.hpp" aoqi@0: #include "classfile/systemDictionary.hpp" aoqi@0: #include "interpreter/interpreter.hpp" aoqi@0: #include "jvmtifiles/jvmtiEnv.hpp" aoqi@0: #include "memory/resourceArea.hpp" aoqi@0: #include "oops/instanceKlass.hpp" aoqi@0: #include "prims/jvmtiAgentThread.hpp" aoqi@0: #include "prims/jvmtiEventController.inline.hpp" aoqi@0: #include "prims/jvmtiImpl.hpp" aoqi@0: #include "prims/jvmtiRedefineClasses.hpp" aoqi@0: #include "runtime/atomic.hpp" aoqi@0: #include "runtime/deoptimization.hpp" aoqi@0: #include "runtime/handles.hpp" aoqi@0: #include "runtime/handles.inline.hpp" aoqi@0: #include "runtime/interfaceSupport.hpp" aoqi@0: #include "runtime/javaCalls.hpp" aoqi@0: #include "runtime/os.hpp" aoqi@0: #include "runtime/serviceThread.hpp" aoqi@0: #include "runtime/signature.hpp" aoqi@0: #include "runtime/thread.inline.hpp" aoqi@0: #include "runtime/vframe.hpp" aoqi@0: #include "runtime/vframe_hp.hpp" aoqi@0: #include "runtime/vm_operations.hpp" aoqi@0: #include "utilities/exceptions.hpp" aoqi@0: aoqi@0: // aoqi@0: // class JvmtiAgentThread aoqi@0: // aoqi@0: // JavaThread used to wrap a thread started by an agent aoqi@0: // using the JVMTI method RunAgentThread. aoqi@0: // aoqi@0: aoqi@0: JvmtiAgentThread::JvmtiAgentThread(JvmtiEnv* env, jvmtiStartFunction start_fn, const void *start_arg) aoqi@0: : JavaThread(start_function_wrapper) { aoqi@0: _env = env; aoqi@0: _start_fn = start_fn; aoqi@0: _start_arg = start_arg; aoqi@0: } aoqi@0: aoqi@0: void aoqi@0: JvmtiAgentThread::start_function_wrapper(JavaThread *thread, TRAPS) { aoqi@0: // It is expected that any Agent threads will be created as aoqi@0: // Java Threads. If this is the case, notification of the creation aoqi@0: // of the thread is given in JavaThread::thread_main(). aoqi@0: assert(thread->is_Java_thread(), "debugger thread should be a Java Thread"); aoqi@0: assert(thread == JavaThread::current(), "sanity check"); aoqi@0: aoqi@0: JvmtiAgentThread *dthread = (JvmtiAgentThread *)thread; aoqi@0: dthread->call_start_function(); aoqi@0: } aoqi@0: aoqi@0: void aoqi@0: JvmtiAgentThread::call_start_function() { aoqi@0: ThreadToNativeFromVM transition(this); aoqi@0: _start_fn(_env->jvmti_external(), jni_environment(), (void*)_start_arg); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: // aoqi@0: // class GrowableCache - private methods aoqi@0: // aoqi@0: aoqi@0: void GrowableCache::recache() { aoqi@0: int len = _elements->length(); aoqi@0: aoqi@0: FREE_C_HEAP_ARRAY(address, _cache, mtInternal); aoqi@0: _cache = NEW_C_HEAP_ARRAY(address,len+1, mtInternal); aoqi@0: aoqi@0: for (int i=0; iat(i)->getCacheValue(); aoqi@0: // aoqi@0: // The cache entry has gone bad. Without a valid frame pointer aoqi@0: // value, the entry is useless so we simply delete it in product aoqi@0: // mode. The call to remove() will rebuild the cache again aoqi@0: // without the bad entry. aoqi@0: // aoqi@0: if (_cache[i] == NULL) { aoqi@0: assert(false, "cannot recache NULL elements"); aoqi@0: remove(i); aoqi@0: return; aoqi@0: } aoqi@0: } aoqi@0: _cache[len] = NULL; aoqi@0: aoqi@0: _listener_fun(_this_obj,_cache); aoqi@0: } aoqi@0: aoqi@0: bool GrowableCache::equals(void* v, GrowableElement *e2) { aoqi@0: GrowableElement *e1 = (GrowableElement *) v; aoqi@0: assert(e1 != NULL, "e1 != NULL"); aoqi@0: assert(e2 != NULL, "e2 != NULL"); aoqi@0: aoqi@0: return e1->equals(e2); aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // class GrowableCache - public methods aoqi@0: // aoqi@0: aoqi@0: GrowableCache::GrowableCache() { aoqi@0: _this_obj = NULL; aoqi@0: _listener_fun = NULL; aoqi@0: _elements = NULL; aoqi@0: _cache = NULL; aoqi@0: } aoqi@0: aoqi@0: GrowableCache::~GrowableCache() { aoqi@0: clear(); aoqi@0: delete _elements; aoqi@0: FREE_C_HEAP_ARRAY(address, _cache, mtInternal); aoqi@0: } aoqi@0: aoqi@0: void GrowableCache::initialize(void *this_obj, void listener_fun(void *, address*) ) { aoqi@0: _this_obj = this_obj; aoqi@0: _listener_fun = listener_fun; aoqi@0: _elements = new (ResourceObj::C_HEAP, mtInternal) GrowableArray(5,true); aoqi@0: recache(); aoqi@0: } aoqi@0: aoqi@0: // number of elements in the collection aoqi@0: int GrowableCache::length() { aoqi@0: return _elements->length(); aoqi@0: } aoqi@0: aoqi@0: // get the value of the index element in the collection aoqi@0: GrowableElement* GrowableCache::at(int index) { aoqi@0: GrowableElement *e = (GrowableElement *) _elements->at(index); aoqi@0: assert(e != NULL, "e != NULL"); aoqi@0: return e; aoqi@0: } aoqi@0: aoqi@0: int GrowableCache::find(GrowableElement* e) { aoqi@0: return _elements->find(e, GrowableCache::equals); aoqi@0: } aoqi@0: aoqi@0: // append a copy of the element to the end of the collection aoqi@0: void GrowableCache::append(GrowableElement* e) { aoqi@0: GrowableElement *new_e = e->clone(); aoqi@0: _elements->append(new_e); aoqi@0: recache(); aoqi@0: } aoqi@0: aoqi@0: // insert a copy of the element using lessthan() aoqi@0: void GrowableCache::insert(GrowableElement* e) { aoqi@0: GrowableElement *new_e = e->clone(); aoqi@0: _elements->append(new_e); aoqi@0: aoqi@0: int n = length()-2; aoqi@0: for (int i=n; i>=0; i--) { aoqi@0: GrowableElement *e1 = _elements->at(i); aoqi@0: GrowableElement *e2 = _elements->at(i+1); aoqi@0: if (e2->lessThan(e1)) { aoqi@0: _elements->at_put(i+1, e1); aoqi@0: _elements->at_put(i, e2); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: recache(); aoqi@0: } aoqi@0: aoqi@0: // remove the element at index aoqi@0: void GrowableCache::remove (int index) { aoqi@0: GrowableElement *e = _elements->at(index); aoqi@0: assert(e != NULL, "e != NULL"); aoqi@0: _elements->remove(e); aoqi@0: delete e; aoqi@0: recache(); aoqi@0: } aoqi@0: aoqi@0: // clear out all elements, release all heap space and aoqi@0: // let our listener know that things have changed. aoqi@0: void GrowableCache::clear() { aoqi@0: int len = _elements->length(); aoqi@0: for (int i=0; iat(i); aoqi@0: } aoqi@0: _elements->clear(); aoqi@0: recache(); aoqi@0: } aoqi@0: aoqi@0: void GrowableCache::oops_do(OopClosure* f) { aoqi@0: int len = _elements->length(); aoqi@0: for (int i=0; iat(i); aoqi@0: e->oops_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void GrowableCache::metadata_do(void f(Metadata*)) { aoqi@0: int len = _elements->length(); aoqi@0: for (int i=0; iat(i); aoqi@0: e->metadata_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void GrowableCache::gc_epilogue() { aoqi@0: int len = _elements->length(); aoqi@0: for (int i=0; iat(i)->getCacheValue(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // class JvmtiBreakpoint aoqi@0: // aoqi@0: aoqi@0: JvmtiBreakpoint::JvmtiBreakpoint() { aoqi@0: _method = NULL; aoqi@0: _bci = 0; aoqi@0: _class_holder = NULL; aoqi@0: } aoqi@0: aoqi@0: JvmtiBreakpoint::JvmtiBreakpoint(Method* m_method, jlocation location) { aoqi@0: _method = m_method; aoqi@0: _class_holder = _method->method_holder()->klass_holder(); aoqi@0: #ifdef CHECK_UNHANDLED_OOPS aoqi@0: // _class_holder can't be wrapped in a Handle, because JvmtiBreakpoints are aoqi@0: // sometimes allocated on the heap. aoqi@0: // aoqi@0: // The code handling JvmtiBreakpoints allocated on the stack can't be aoqi@0: // interrupted by a GC until _class_holder is reachable by the GC via the aoqi@0: // oops_do method. aoqi@0: Thread::current()->allow_unhandled_oop(&_class_holder); aoqi@0: #endif // CHECK_UNHANDLED_OOPS aoqi@0: assert(_method != NULL, "_method != NULL"); aoqi@0: _bci = (int) location; aoqi@0: assert(_bci >= 0, "_bci >= 0"); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoint::copy(JvmtiBreakpoint& bp) { aoqi@0: _method = bp._method; aoqi@0: _bci = bp._bci; aoqi@0: _class_holder = bp._class_holder; aoqi@0: } aoqi@0: aoqi@0: bool JvmtiBreakpoint::lessThan(JvmtiBreakpoint& bp) { aoqi@0: Unimplemented(); aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: bool JvmtiBreakpoint::equals(JvmtiBreakpoint& bp) { aoqi@0: return _method == bp._method aoqi@0: && _bci == bp._bci; aoqi@0: } aoqi@0: aoqi@0: bool JvmtiBreakpoint::is_valid() { aoqi@0: // class loader can be NULL aoqi@0: return _method != NULL && aoqi@0: _bci >= 0; aoqi@0: } aoqi@0: aoqi@0: address JvmtiBreakpoint::getBcp() { aoqi@0: return _method->bcp_from(_bci); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoint::each_method_version_do(method_action meth_act) { aoqi@0: ((Method*)_method->*meth_act)(_bci); aoqi@0: aoqi@0: // add/remove breakpoint to/from versions of the method that aoqi@0: // are EMCP. Directly or transitively obsolete methods are aoqi@0: // not saved in the PreviousVersionNodes. aoqi@0: Thread *thread = Thread::current(); aoqi@0: instanceKlassHandle ikh = instanceKlassHandle(thread, _method->method_holder()); aoqi@0: Symbol* m_name = _method->name(); aoqi@0: Symbol* m_signature = _method->signature(); aoqi@0: aoqi@0: // search previous versions if they exist aoqi@0: PreviousVersionWalker pvw(thread, (InstanceKlass *)ikh()); aoqi@0: for (PreviousVersionNode * pv_node = pvw.next_previous_version(); aoqi@0: pv_node != NULL; pv_node = pvw.next_previous_version()) { aoqi@0: GrowableArray* methods = pv_node->prev_EMCP_methods(); aoqi@0: aoqi@0: if (methods == NULL) { aoqi@0: // We have run into a PreviousVersion generation where aoqi@0: // all methods were made obsolete during that generation's aoqi@0: // RedefineClasses() operation. At the time of that aoqi@0: // operation, all EMCP methods were flushed so we don't aoqi@0: // have to go back any further. aoqi@0: // aoqi@0: // A NULL methods array is different than an empty methods aoqi@0: // array. We cannot infer any optimizations about older aoqi@0: // generations from an empty methods array for the current aoqi@0: // generation. aoqi@0: break; aoqi@0: } aoqi@0: aoqi@0: for (int i = methods->length() - 1; i >= 0; i--) { aoqi@0: Method* method = methods->at(i); aoqi@0: // obsolete methods that are running are not deleted from aoqi@0: // previous version array, but they are skipped here. aoqi@0: if (!method->is_obsolete() && aoqi@0: method->name() == m_name && aoqi@0: method->signature() == m_signature) { aoqi@0: RC_TRACE(0x00000800, ("%sing breakpoint in %s(%s)", aoqi@0: meth_act == &Method::set_breakpoint ? "sett" : "clear", aoqi@0: method->name()->as_C_string(), aoqi@0: method->signature()->as_C_string())); aoqi@0: aoqi@0: (method->*meth_act)(_bci); aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoint::set() { aoqi@0: each_method_version_do(&Method::set_breakpoint); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoint::clear() { aoqi@0: each_method_version_do(&Method::clear_breakpoint); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoint::print() { aoqi@0: #ifndef PRODUCT aoqi@0: const char *class_name = (_method == NULL) ? "NULL" : _method->klass_name()->as_C_string(); aoqi@0: const char *method_name = (_method == NULL) ? "NULL" : _method->name()->as_C_string(); aoqi@0: aoqi@0: tty->print("Breakpoint(%s,%s,%d,%p)",class_name, method_name, _bci, getBcp()); aoqi@0: #endif aoqi@0: } aoqi@0: aoqi@0: aoqi@0: // aoqi@0: // class VM_ChangeBreakpoints aoqi@0: // aoqi@0: // Modify the Breakpoints data structure at a safepoint aoqi@0: // aoqi@0: aoqi@0: void VM_ChangeBreakpoints::doit() { aoqi@0: switch (_operation) { aoqi@0: case SET_BREAKPOINT: aoqi@0: _breakpoints->set_at_safepoint(*_bp); aoqi@0: break; aoqi@0: case CLEAR_BREAKPOINT: aoqi@0: _breakpoints->clear_at_safepoint(*_bp); aoqi@0: break; aoqi@0: default: aoqi@0: assert(false, "Unknown operation"); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void VM_ChangeBreakpoints::oops_do(OopClosure* f) { aoqi@0: // The JvmtiBreakpoints in _breakpoints will be visited via aoqi@0: // JvmtiExport::oops_do. aoqi@0: if (_bp != NULL) { aoqi@0: _bp->oops_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void VM_ChangeBreakpoints::metadata_do(void f(Metadata*)) { aoqi@0: // Walk metadata in breakpoints to keep from being deallocated with RedefineClasses aoqi@0: if (_bp != NULL) { aoqi@0: _bp->metadata_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // class JvmtiBreakpoints aoqi@0: // aoqi@0: // a JVMTI internal collection of JvmtiBreakpoint aoqi@0: // aoqi@0: aoqi@0: JvmtiBreakpoints::JvmtiBreakpoints(void listener_fun(void *,address *)) { aoqi@0: _bps.initialize(this,listener_fun); aoqi@0: } aoqi@0: aoqi@0: JvmtiBreakpoints:: ~JvmtiBreakpoints() {} aoqi@0: aoqi@0: void JvmtiBreakpoints::oops_do(OopClosure* f) { aoqi@0: _bps.oops_do(f); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoints::metadata_do(void f(Metadata*)) { aoqi@0: _bps.metadata_do(f); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoints::gc_epilogue() { aoqi@0: _bps.gc_epilogue(); aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoints::print() { aoqi@0: #ifndef PRODUCT aoqi@0: ResourceMark rm; aoqi@0: aoqi@0: int n = _bps.length(); aoqi@0: for (int i=0; iprint("%d: ", i); aoqi@0: bp.print(); aoqi@0: tty->cr(); aoqi@0: } aoqi@0: #endif aoqi@0: } aoqi@0: aoqi@0: aoqi@0: void JvmtiBreakpoints::set_at_safepoint(JvmtiBreakpoint& bp) { aoqi@0: assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint"); aoqi@0: aoqi@0: int i = _bps.find(bp); aoqi@0: if (i == -1) { aoqi@0: _bps.append(bp); aoqi@0: bp.set(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoints::clear_at_safepoint(JvmtiBreakpoint& bp) { aoqi@0: assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint"); aoqi@0: aoqi@0: int i = _bps.find(bp); aoqi@0: if (i != -1) { aoqi@0: _bps.remove(i); aoqi@0: bp.clear(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: int JvmtiBreakpoints::length() { return _bps.length(); } aoqi@0: aoqi@0: int JvmtiBreakpoints::set(JvmtiBreakpoint& bp) { aoqi@0: if ( _bps.find(bp) != -1) { aoqi@0: return JVMTI_ERROR_DUPLICATE; aoqi@0: } aoqi@0: VM_ChangeBreakpoints set_breakpoint(VM_ChangeBreakpoints::SET_BREAKPOINT, &bp); aoqi@0: VMThread::execute(&set_breakpoint); aoqi@0: return JVMTI_ERROR_NONE; aoqi@0: } aoqi@0: aoqi@0: int JvmtiBreakpoints::clear(JvmtiBreakpoint& bp) { aoqi@0: if ( _bps.find(bp) == -1) { aoqi@0: return JVMTI_ERROR_NOT_FOUND; aoqi@0: } aoqi@0: aoqi@0: VM_ChangeBreakpoints clear_breakpoint(VM_ChangeBreakpoints::CLEAR_BREAKPOINT, &bp); aoqi@0: VMThread::execute(&clear_breakpoint); aoqi@0: return JVMTI_ERROR_NONE; aoqi@0: } aoqi@0: aoqi@0: void JvmtiBreakpoints::clearall_in_class_at_safepoint(Klass* klass) { aoqi@0: bool changed = true; aoqi@0: // We are going to run thru the list of bkpts aoqi@0: // and delete some. This deletion probably alters aoqi@0: // the list in some implementation defined way such aoqi@0: // that when we delete entry i, the next entry might aoqi@0: // no longer be at i+1. To be safe, each time we delete aoqi@0: // an entry, we'll just start again from the beginning. aoqi@0: // We'll stop when we make a pass thru the whole list without aoqi@0: // deleting anything. aoqi@0: while (changed) { aoqi@0: int len = _bps.length(); aoqi@0: changed = false; aoqi@0: for (int i = 0; i < len; i++) { aoqi@0: JvmtiBreakpoint& bp = _bps.at(i); aoqi@0: if (bp.method()->method_holder() == klass) { aoqi@0: bp.clear(); aoqi@0: _bps.remove(i); aoqi@0: // This changed 'i' so we have to start over. aoqi@0: changed = true; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: // class JvmtiCurrentBreakpoints aoqi@0: // aoqi@0: aoqi@0: JvmtiBreakpoints *JvmtiCurrentBreakpoints::_jvmti_breakpoints = NULL; aoqi@0: address * JvmtiCurrentBreakpoints::_breakpoint_list = NULL; aoqi@0: aoqi@0: aoqi@0: JvmtiBreakpoints& JvmtiCurrentBreakpoints::get_jvmti_breakpoints() { aoqi@0: if (_jvmti_breakpoints != NULL) return (*_jvmti_breakpoints); aoqi@0: _jvmti_breakpoints = new JvmtiBreakpoints(listener_fun); aoqi@0: assert(_jvmti_breakpoints != NULL, "_jvmti_breakpoints != NULL"); aoqi@0: return (*_jvmti_breakpoints); aoqi@0: } aoqi@0: aoqi@0: void JvmtiCurrentBreakpoints::listener_fun(void *this_obj, address *cache) { aoqi@0: JvmtiBreakpoints *this_jvmti = (JvmtiBreakpoints *) this_obj; aoqi@0: assert(this_jvmti != NULL, "this_jvmti != NULL"); aoqi@0: aoqi@0: debug_only(int n = this_jvmti->length();); aoqi@0: assert(cache[n] == NULL, "cache must be NULL terminated"); aoqi@0: aoqi@0: set_breakpoint_list(cache); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: void JvmtiCurrentBreakpoints::oops_do(OopClosure* f) { aoqi@0: if (_jvmti_breakpoints != NULL) { aoqi@0: _jvmti_breakpoints->oops_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void JvmtiCurrentBreakpoints::metadata_do(void f(Metadata*)) { aoqi@0: if (_jvmti_breakpoints != NULL) { aoqi@0: _jvmti_breakpoints->metadata_do(f); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: void JvmtiCurrentBreakpoints::gc_epilogue() { aoqi@0: if (_jvmti_breakpoints != NULL) { aoqi@0: _jvmti_breakpoints->gc_epilogue(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /////////////////////////////////////////////////////////////// aoqi@0: // aoqi@0: // class VM_GetOrSetLocal aoqi@0: // aoqi@0: aoqi@0: // Constructor for non-object getter aoqi@0: VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, int index, BasicType type) aoqi@0: : _thread(thread) aoqi@0: , _calling_thread(NULL) aoqi@0: , _depth(depth) aoqi@0: , _index(index) aoqi@0: , _type(type) aoqi@0: , _set(false) aoqi@0: , _jvf(NULL) aoqi@0: , _result(JVMTI_ERROR_NONE) aoqi@0: { aoqi@0: } aoqi@0: aoqi@0: // Constructor for object or non-object setter aoqi@0: VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, int index, BasicType type, jvalue value) aoqi@0: : _thread(thread) aoqi@0: , _calling_thread(NULL) aoqi@0: , _depth(depth) aoqi@0: , _index(index) aoqi@0: , _type(type) aoqi@0: , _value(value) aoqi@0: , _set(true) aoqi@0: , _jvf(NULL) aoqi@0: , _result(JVMTI_ERROR_NONE) aoqi@0: { aoqi@0: } aoqi@0: aoqi@0: // Constructor for object getter aoqi@0: VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, JavaThread* calling_thread, jint depth, int index) aoqi@0: : _thread(thread) aoqi@0: , _calling_thread(calling_thread) aoqi@0: , _depth(depth) aoqi@0: , _index(index) aoqi@0: , _type(T_OBJECT) aoqi@0: , _set(false) aoqi@0: , _jvf(NULL) aoqi@0: , _result(JVMTI_ERROR_NONE) aoqi@0: { aoqi@0: } aoqi@0: aoqi@0: vframe *VM_GetOrSetLocal::get_vframe() { aoqi@0: if (!_thread->has_last_Java_frame()) { aoqi@0: return NULL; aoqi@0: } aoqi@0: RegisterMap reg_map(_thread); aoqi@0: vframe *vf = _thread->last_java_vframe(®_map); aoqi@0: int d = 0; aoqi@0: while ((vf != NULL) && (d < _depth)) { aoqi@0: vf = vf->java_sender(); aoqi@0: d++; aoqi@0: } aoqi@0: return vf; aoqi@0: } aoqi@0: aoqi@0: javaVFrame *VM_GetOrSetLocal::get_java_vframe() { aoqi@0: vframe* vf = get_vframe(); aoqi@0: if (vf == NULL) { aoqi@0: _result = JVMTI_ERROR_NO_MORE_FRAMES; aoqi@0: return NULL; aoqi@0: } aoqi@0: javaVFrame *jvf = (javaVFrame*)vf; aoqi@0: aoqi@0: if (!vf->is_java_frame()) { aoqi@0: _result = JVMTI_ERROR_OPAQUE_FRAME; aoqi@0: return NULL; aoqi@0: } aoqi@0: return jvf; aoqi@0: } aoqi@0: aoqi@0: // Check that the klass is assignable to a type with the given signature. aoqi@0: // Another solution could be to use the function Klass::is_subtype_of(type). aoqi@0: // But the type class can be forced to load/initialize eagerly in such a case. aoqi@0: // This may cause unexpected consequences like CFLH or class-init JVMTI events. aoqi@0: // It is better to avoid such a behavior. aoqi@0: bool VM_GetOrSetLocal::is_assignable(const char* ty_sign, Klass* klass, Thread* thread) { aoqi@0: assert(ty_sign != NULL, "type signature must not be NULL"); aoqi@0: assert(thread != NULL, "thread must not be NULL"); aoqi@0: assert(klass != NULL, "klass must not be NULL"); aoqi@0: aoqi@0: int len = (int) strlen(ty_sign); aoqi@0: if (ty_sign[0] == 'L' && ty_sign[len-1] == ';') { // Need pure class/interface name aoqi@0: ty_sign++; aoqi@0: len -= 2; aoqi@0: } aoqi@0: TempNewSymbol ty_sym = SymbolTable::new_symbol(ty_sign, len, thread); aoqi@0: if (klass->name() == ty_sym) { aoqi@0: return true; aoqi@0: } aoqi@0: // Compare primary supers aoqi@0: int super_depth = klass->super_depth(); aoqi@0: int idx; aoqi@0: for (idx = 0; idx < super_depth; idx++) { aoqi@0: if (klass->primary_super_of_depth(idx)->name() == ty_sym) { aoqi@0: return true; aoqi@0: } aoqi@0: } aoqi@0: // Compare secondary supers aoqi@0: Array* sec_supers = klass->secondary_supers(); aoqi@0: for (idx = 0; idx < sec_supers->length(); idx++) { aoqi@0: if (((Klass*) sec_supers->at(idx))->name() == ty_sym) { aoqi@0: return true; aoqi@0: } aoqi@0: } aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: // Checks error conditions: aoqi@0: // JVMTI_ERROR_INVALID_SLOT aoqi@0: // JVMTI_ERROR_TYPE_MISMATCH aoqi@0: // Returns: 'true' - everything is Ok, 'false' - error code aoqi@0: aoqi@0: bool VM_GetOrSetLocal::check_slot_type(javaVFrame* jvf) { aoqi@0: Method* method_oop = jvf->method(); aoqi@0: if (!method_oop->has_localvariable_table()) { aoqi@0: // Just to check index boundaries aoqi@0: jint extra_slot = (_type == T_LONG || _type == T_DOUBLE) ? 1 : 0; aoqi@0: if (_index < 0 || _index + extra_slot >= method_oop->max_locals()) { aoqi@0: _result = JVMTI_ERROR_INVALID_SLOT; aoqi@0: return false; aoqi@0: } aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: jint num_entries = method_oop->localvariable_table_length(); aoqi@0: if (num_entries == 0) { aoqi@0: _result = JVMTI_ERROR_INVALID_SLOT; aoqi@0: return false; // There are no slots aoqi@0: } aoqi@0: int signature_idx = -1; aoqi@0: int vf_bci = jvf->bci(); aoqi@0: LocalVariableTableElement* table = method_oop->localvariable_table_start(); aoqi@0: for (int i = 0; i < num_entries; i++) { aoqi@0: int start_bci = table[i].start_bci; aoqi@0: int end_bci = start_bci + table[i].length; aoqi@0: aoqi@0: // Here we assume that locations of LVT entries aoqi@0: // with the same slot number cannot be overlapped aoqi@0: if (_index == (jint) table[i].slot && start_bci <= vf_bci && vf_bci <= end_bci) { aoqi@0: signature_idx = (int) table[i].descriptor_cp_index; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: if (signature_idx == -1) { aoqi@0: _result = JVMTI_ERROR_INVALID_SLOT; aoqi@0: return false; // Incorrect slot index aoqi@0: } aoqi@0: Symbol* sign_sym = method_oop->constants()->symbol_at(signature_idx); aoqi@0: const char* signature = (const char *) sign_sym->as_utf8(); aoqi@0: BasicType slot_type = char2type(signature[0]); aoqi@0: aoqi@0: switch (slot_type) { aoqi@0: case T_BYTE: aoqi@0: case T_SHORT: aoqi@0: case T_CHAR: aoqi@0: case T_BOOLEAN: aoqi@0: slot_type = T_INT; aoqi@0: break; aoqi@0: case T_ARRAY: aoqi@0: slot_type = T_OBJECT; aoqi@0: break; aoqi@0: }; aoqi@0: if (_type != slot_type) { aoqi@0: _result = JVMTI_ERROR_TYPE_MISMATCH; aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: jobject jobj = _value.l; aoqi@0: if (_set && slot_type == T_OBJECT && jobj != NULL) { // NULL reference is allowed aoqi@0: // Check that the jobject class matches the return type signature. aoqi@0: JavaThread* cur_thread = JavaThread::current(); aoqi@0: HandleMark hm(cur_thread); aoqi@0: aoqi@0: Handle obj = Handle(cur_thread, JNIHandles::resolve_external_guard(jobj)); aoqi@0: NULL_CHECK(obj, (_result = JVMTI_ERROR_INVALID_OBJECT, false)); aoqi@0: KlassHandle ob_kh = KlassHandle(cur_thread, obj->klass()); aoqi@0: NULL_CHECK(ob_kh, (_result = JVMTI_ERROR_INVALID_OBJECT, false)); aoqi@0: aoqi@0: if (!is_assignable(signature, ob_kh(), cur_thread)) { aoqi@0: _result = JVMTI_ERROR_TYPE_MISMATCH; aoqi@0: return false; aoqi@0: } aoqi@0: } aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: static bool can_be_deoptimized(vframe* vf) { aoqi@0: return (vf->is_compiled_frame() && vf->fr().can_be_deoptimized()); aoqi@0: } aoqi@0: aoqi@0: bool VM_GetOrSetLocal::doit_prologue() { aoqi@0: _jvf = get_java_vframe(); aoqi@0: NULL_CHECK(_jvf, false); aoqi@0: aoqi@0: if (_jvf->method()->is_native()) { aoqi@0: if (getting_receiver() && !_jvf->method()->is_static()) { aoqi@0: return true; aoqi@0: } else { aoqi@0: _result = JVMTI_ERROR_OPAQUE_FRAME; aoqi@0: return false; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (!check_slot_type(_jvf)) { aoqi@0: return false; aoqi@0: } aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: void VM_GetOrSetLocal::doit() { aoqi@0: if (_set) { aoqi@0: // Force deoptimization of frame if compiled because it's aoqi@0: // possible the compiler emitted some locals as constant values, aoqi@0: // meaning they are not mutable. aoqi@0: if (can_be_deoptimized(_jvf)) { aoqi@0: aoqi@0: // Schedule deoptimization so that eventually the local aoqi@0: // update will be written to an interpreter frame. aoqi@0: Deoptimization::deoptimize_frame(_jvf->thread(), _jvf->fr().id()); aoqi@0: aoqi@0: // Now store a new value for the local which will be applied aoqi@0: // once deoptimization occurs. Note however that while this aoqi@0: // write is deferred until deoptimization actually happens aoqi@0: // can vframe created after this point will have its locals aoqi@0: // reflecting this update so as far as anyone can see the aoqi@0: // write has already taken place. aoqi@0: aoqi@0: // If we are updating an oop then get the oop from the handle aoqi@0: // since the handle will be long gone by the time the deopt aoqi@0: // happens. The oop stored in the deferred local will be aoqi@0: // gc'd on its own. aoqi@0: if (_type == T_OBJECT) { aoqi@0: _value.l = (jobject) (JNIHandles::resolve_external_guard(_value.l)); aoqi@0: } aoqi@0: // Re-read the vframe so we can see that it is deoptimized aoqi@0: // [ Only need because of assert in update_local() ] aoqi@0: _jvf = get_java_vframe(); aoqi@0: ((compiledVFrame*)_jvf)->update_local(_type, _index, _value); aoqi@0: return; aoqi@0: } aoqi@0: StackValueCollection *locals = _jvf->locals(); aoqi@0: HandleMark hm; aoqi@0: aoqi@0: switch (_type) { aoqi@0: case T_INT: locals->set_int_at (_index, _value.i); break; aoqi@0: case T_LONG: locals->set_long_at (_index, _value.j); break; aoqi@0: case T_FLOAT: locals->set_float_at (_index, _value.f); break; aoqi@0: case T_DOUBLE: locals->set_double_at(_index, _value.d); break; aoqi@0: case T_OBJECT: { aoqi@0: Handle ob_h(JNIHandles::resolve_external_guard(_value.l)); aoqi@0: locals->set_obj_at (_index, ob_h); aoqi@0: break; aoqi@0: } aoqi@0: default: ShouldNotReachHere(); aoqi@0: } aoqi@0: _jvf->set_locals(locals); aoqi@0: } else { aoqi@0: if (_jvf->method()->is_native() && _jvf->is_compiled_frame()) { aoqi@0: assert(getting_receiver(), "Can only get here when getting receiver"); aoqi@0: oop receiver = _jvf->fr().get_native_receiver(); aoqi@0: _value.l = JNIHandles::make_local(_calling_thread, receiver); aoqi@0: } else { aoqi@0: StackValueCollection *locals = _jvf->locals(); aoqi@0: aoqi@0: if (locals->at(_index)->type() == T_CONFLICT) { aoqi@0: memset(&_value, 0, sizeof(_value)); aoqi@0: _value.l = NULL; aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: switch (_type) { aoqi@0: case T_INT: _value.i = locals->int_at (_index); break; aoqi@0: case T_LONG: _value.j = locals->long_at (_index); break; aoqi@0: case T_FLOAT: _value.f = locals->float_at (_index); break; aoqi@0: case T_DOUBLE: _value.d = locals->double_at(_index); break; aoqi@0: case T_OBJECT: { aoqi@0: // Wrap the oop to be returned in a local JNI handle since aoqi@0: // oops_do() no longer applies after doit() is finished. aoqi@0: oop obj = locals->obj_at(_index)(); aoqi@0: _value.l = JNIHandles::make_local(_calling_thread, obj); aoqi@0: break; aoqi@0: } aoqi@0: default: ShouldNotReachHere(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: aoqi@0: bool VM_GetOrSetLocal::allow_nested_vm_operations() const { aoqi@0: return true; // May need to deoptimize aoqi@0: } aoqi@0: aoqi@0: aoqi@0: VM_GetReceiver::VM_GetReceiver( aoqi@0: JavaThread* thread, JavaThread* caller_thread, jint depth) aoqi@0: : VM_GetOrSetLocal(thread, caller_thread, depth, 0) {} aoqi@0: aoqi@0: ///////////////////////////////////////////////////////////////////////////////////////// aoqi@0: aoqi@0: // aoqi@0: // class JvmtiSuspendControl - see comments in jvmtiImpl.hpp aoqi@0: // aoqi@0: aoqi@0: bool JvmtiSuspendControl::suspend(JavaThread *java_thread) { aoqi@0: // external suspend should have caught suspending a thread twice aoqi@0: aoqi@0: // Immediate suspension required for JPDA back-end so JVMTI agent threads do aoqi@0: // not deadlock due to later suspension on transitions while holding aoqi@0: // raw monitors. Passing true causes the immediate suspension. aoqi@0: // java_suspend() will catch threads in the process of exiting aoqi@0: // and will ignore them. aoqi@0: java_thread->java_suspend(); aoqi@0: aoqi@0: // It would be nice to have the following assertion in all the time, aoqi@0: // but it is possible for a racing resume request to have resumed aoqi@0: // this thread right after we suspended it. Temporarily enable this aoqi@0: // assertion if you are chasing a different kind of bug. aoqi@0: // aoqi@0: // assert(java_lang_Thread::thread(java_thread->threadObj()) == NULL || aoqi@0: // java_thread->is_being_ext_suspended(), "thread is not suspended"); aoqi@0: aoqi@0: if (java_lang_Thread::thread(java_thread->threadObj()) == NULL) { aoqi@0: // check again because we can get delayed in java_suspend(): aoqi@0: // the thread is in process of exiting. aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: bool JvmtiSuspendControl::resume(JavaThread *java_thread) { aoqi@0: // external suspend should have caught resuming a thread twice aoqi@0: assert(java_thread->is_being_ext_suspended(), "thread should be suspended"); aoqi@0: aoqi@0: // resume thread aoqi@0: { aoqi@0: // must always grab Threads_lock, see JVM_SuspendThread aoqi@0: MutexLocker ml(Threads_lock); aoqi@0: java_thread->java_resume(); aoqi@0: } aoqi@0: aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: aoqi@0: void JvmtiSuspendControl::print() { aoqi@0: #ifndef PRODUCT aoqi@0: MutexLocker mu(Threads_lock); aoqi@0: ResourceMark rm; aoqi@0: aoqi@0: tty->print("Suspended Threads: ["); aoqi@0: for (JavaThread *thread = Threads::first(); thread != NULL; thread = thread->next()) { aoqi@0: #ifdef JVMTI_TRACE aoqi@0: const char *name = JvmtiTrace::safe_get_thread_name(thread); aoqi@0: #else aoqi@0: const char *name = ""; aoqi@0: #endif /*JVMTI_TRACE */ aoqi@0: tty->print("%s(%c ", name, thread->is_being_ext_suspended() ? 'S' : '_'); aoqi@0: if (!thread->has_last_Java_frame()) { aoqi@0: tty->print("no stack"); aoqi@0: } aoqi@0: tty->print(") "); aoqi@0: } aoqi@0: tty->print_cr("]"); aoqi@0: #endif aoqi@0: } aoqi@0: aoqi@0: JvmtiDeferredEvent JvmtiDeferredEvent::compiled_method_load_event( aoqi@0: nmethod* nm) { aoqi@0: JvmtiDeferredEvent event = JvmtiDeferredEvent(TYPE_COMPILED_METHOD_LOAD); aoqi@0: event._event_data.compiled_method_load = nm; aoqi@0: // Keep the nmethod alive until the ServiceThread can process aoqi@0: // this deferred event. aoqi@0: nmethodLocker::lock_nmethod(nm); aoqi@0: return event; aoqi@0: } aoqi@0: aoqi@0: JvmtiDeferredEvent JvmtiDeferredEvent::compiled_method_unload_event( aoqi@0: nmethod* nm, jmethodID id, const void* code) { aoqi@0: JvmtiDeferredEvent event = JvmtiDeferredEvent(TYPE_COMPILED_METHOD_UNLOAD); aoqi@0: event._event_data.compiled_method_unload.nm = nm; aoqi@0: event._event_data.compiled_method_unload.method_id = id; aoqi@0: event._event_data.compiled_method_unload.code_begin = code; aoqi@0: // Keep the nmethod alive until the ServiceThread can process aoqi@0: // this deferred event. This will keep the memory for the aoqi@0: // generated code from being reused too early. We pass aoqi@0: // zombie_ok == true here so that our nmethod that was just aoqi@0: // made into a zombie can be locked. aoqi@0: nmethodLocker::lock_nmethod(nm, true /* zombie_ok */); aoqi@0: return event; aoqi@0: } aoqi@0: aoqi@0: JvmtiDeferredEvent JvmtiDeferredEvent::dynamic_code_generated_event( aoqi@0: const char* name, const void* code_begin, const void* code_end) { aoqi@0: JvmtiDeferredEvent event = JvmtiDeferredEvent(TYPE_DYNAMIC_CODE_GENERATED); aoqi@0: // Need to make a copy of the name since we don't know how long aoqi@0: // the event poster will keep it around after we enqueue the aoqi@0: // deferred event and return. strdup() failure is handled in aoqi@0: // the post() routine below. aoqi@0: event._event_data.dynamic_code_generated.name = os::strdup(name); aoqi@0: event._event_data.dynamic_code_generated.code_begin = code_begin; aoqi@0: event._event_data.dynamic_code_generated.code_end = code_end; aoqi@0: return event; aoqi@0: } aoqi@0: aoqi@0: void JvmtiDeferredEvent::post() { aoqi@0: assert(ServiceThread::is_service_thread(Thread::current()), aoqi@0: "Service thread must post enqueued events"); aoqi@0: switch(_type) { aoqi@0: case TYPE_COMPILED_METHOD_LOAD: { aoqi@0: nmethod* nm = _event_data.compiled_method_load; aoqi@0: JvmtiExport::post_compiled_method_load(nm); aoqi@0: // done with the deferred event so unlock the nmethod aoqi@0: nmethodLocker::unlock_nmethod(nm); aoqi@0: break; aoqi@0: } aoqi@0: case TYPE_COMPILED_METHOD_UNLOAD: { aoqi@0: nmethod* nm = _event_data.compiled_method_unload.nm; aoqi@0: JvmtiExport::post_compiled_method_unload( aoqi@0: _event_data.compiled_method_unload.method_id, aoqi@0: _event_data.compiled_method_unload.code_begin); aoqi@0: // done with the deferred event so unlock the nmethod aoqi@0: nmethodLocker::unlock_nmethod(nm); aoqi@0: break; aoqi@0: } aoqi@0: case TYPE_DYNAMIC_CODE_GENERATED: { aoqi@0: JvmtiExport::post_dynamic_code_generated_internal( aoqi@0: // if strdup failed give the event a default name aoqi@0: (_event_data.dynamic_code_generated.name == NULL) aoqi@0: ? "unknown_code" : _event_data.dynamic_code_generated.name, aoqi@0: _event_data.dynamic_code_generated.code_begin, aoqi@0: _event_data.dynamic_code_generated.code_end); aoqi@0: if (_event_data.dynamic_code_generated.name != NULL) { aoqi@0: // release our copy aoqi@0: os::free((void *)_event_data.dynamic_code_generated.name); aoqi@0: } aoqi@0: break; aoqi@0: } aoqi@0: default: aoqi@0: ShouldNotReachHere(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: JvmtiDeferredEventQueue::QueueNode* JvmtiDeferredEventQueue::_queue_tail = NULL; aoqi@0: JvmtiDeferredEventQueue::QueueNode* JvmtiDeferredEventQueue::_queue_head = NULL; aoqi@0: aoqi@0: volatile JvmtiDeferredEventQueue::QueueNode* aoqi@0: JvmtiDeferredEventQueue::_pending_list = NULL; aoqi@0: aoqi@0: bool JvmtiDeferredEventQueue::has_events() { aoqi@0: assert(Service_lock->owned_by_self(), "Must own Service_lock"); aoqi@0: return _queue_head != NULL || _pending_list != NULL; aoqi@0: } aoqi@0: aoqi@0: void JvmtiDeferredEventQueue::enqueue(const JvmtiDeferredEvent& event) { aoqi@0: assert(Service_lock->owned_by_self(), "Must own Service_lock"); aoqi@0: aoqi@0: process_pending_events(); aoqi@0: aoqi@0: // Events get added to the end of the queue (and are pulled off the front). aoqi@0: QueueNode* node = new QueueNode(event); aoqi@0: if (_queue_tail == NULL) { aoqi@0: _queue_tail = _queue_head = node; aoqi@0: } else { aoqi@0: assert(_queue_tail->next() == NULL, "Must be the last element in the list"); aoqi@0: _queue_tail->set_next(node); aoqi@0: _queue_tail = node; aoqi@0: } aoqi@0: aoqi@0: Service_lock->notify_all(); aoqi@0: assert((_queue_head == NULL) == (_queue_tail == NULL), aoqi@0: "Inconsistent queue markers"); aoqi@0: } aoqi@0: aoqi@0: JvmtiDeferredEvent JvmtiDeferredEventQueue::dequeue() { aoqi@0: assert(Service_lock->owned_by_self(), "Must own Service_lock"); aoqi@0: aoqi@0: process_pending_events(); aoqi@0: aoqi@0: assert(_queue_head != NULL, "Nothing to dequeue"); aoqi@0: aoqi@0: if (_queue_head == NULL) { aoqi@0: // Just in case this happens in product; it shouldn't but let's not crash aoqi@0: return JvmtiDeferredEvent(); aoqi@0: } aoqi@0: aoqi@0: QueueNode* node = _queue_head; aoqi@0: _queue_head = _queue_head->next(); aoqi@0: if (_queue_head == NULL) { aoqi@0: _queue_tail = NULL; aoqi@0: } aoqi@0: aoqi@0: assert((_queue_head == NULL) == (_queue_tail == NULL), aoqi@0: "Inconsistent queue markers"); aoqi@0: aoqi@0: JvmtiDeferredEvent event = node->event(); aoqi@0: delete node; aoqi@0: return event; aoqi@0: } aoqi@0: aoqi@0: void JvmtiDeferredEventQueue::add_pending_event( aoqi@0: const JvmtiDeferredEvent& event) { aoqi@0: aoqi@0: QueueNode* node = new QueueNode(event); aoqi@0: aoqi@0: bool success = false; aoqi@0: QueueNode* prev_value = (QueueNode*)_pending_list; aoqi@0: do { aoqi@0: node->set_next(prev_value); aoqi@0: prev_value = (QueueNode*)Atomic::cmpxchg_ptr( aoqi@0: (void*)node, (volatile void*)&_pending_list, (void*)node->next()); aoqi@0: } while (prev_value != node->next()); aoqi@0: } aoqi@0: aoqi@0: // This method transfers any events that were added by someone NOT holding aoqi@0: // the lock into the mainline queue. aoqi@0: void JvmtiDeferredEventQueue::process_pending_events() { aoqi@0: assert(Service_lock->owned_by_self(), "Must own Service_lock"); aoqi@0: aoqi@0: if (_pending_list != NULL) { aoqi@0: QueueNode* head = aoqi@0: (QueueNode*)Atomic::xchg_ptr(NULL, (volatile void*)&_pending_list); aoqi@0: aoqi@0: assert((_queue_head == NULL) == (_queue_tail == NULL), aoqi@0: "Inconsistent queue markers"); aoqi@0: aoqi@0: if (head != NULL) { aoqi@0: // Since we've treated the pending list as a stack (with newer aoqi@0: // events at the beginning), we need to join the bottom of the stack aoqi@0: // with the 'tail' of the queue in order to get the events in the aoqi@0: // right order. We do this by reversing the pending list and appending aoqi@0: // it to the queue. aoqi@0: aoqi@0: QueueNode* new_tail = head; aoqi@0: QueueNode* new_head = NULL; aoqi@0: aoqi@0: // This reverses the list aoqi@0: QueueNode* prev = new_tail; aoqi@0: QueueNode* node = new_tail->next(); aoqi@0: new_tail->set_next(NULL); aoqi@0: while (node != NULL) { aoqi@0: QueueNode* next = node->next(); aoqi@0: node->set_next(prev); aoqi@0: prev = node; aoqi@0: node = next; aoqi@0: } aoqi@0: new_head = prev; aoqi@0: aoqi@0: // Now append the new list to the queue aoqi@0: if (_queue_tail != NULL) { aoqi@0: _queue_tail->set_next(new_head); aoqi@0: } else { // _queue_head == NULL aoqi@0: _queue_head = new_head; aoqi@0: } aoqi@0: _queue_tail = new_tail; aoqi@0: } aoqi@0: } aoqi@0: }