/* * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.jdi; import com.sun.jdi.*; import java.util.List; import java.util.Map; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Collections; public class StackFrameImpl extends MirrorImpl implements StackFrame, ThreadListener { /* Once false, frame should not be used. * access synchronized on (vm.state()) */ private boolean isValid = true; private final ThreadReferenceImpl thread; private final long id; private final Location location; private Map<String, LocalVariable> visibleVariables = null; private ObjectReference thisObject = null; StackFrameImpl(VirtualMachine vm, ThreadReferenceImpl thread, long id, Location location) { super(vm); this.thread = thread; this.id = id; this.location = location; thread.addListener(this); } /* * ThreadListener implementation * Must be synchronized since we must protect against * sending defunct (isValid == false) stack ids to the back-end. */ public boolean threadResumable(ThreadAction action) { synchronized (vm.state()) { if (isValid) { isValid = false; return false; /* remove this stack frame as a listener */ } else { throw new InternalException( "Invalid stack frame thread listener"); } } } void validateStackFrame() { if (!isValid) { throw new InvalidStackFrameException("Thread has been resumed"); } } /** * Return the frame location. * Need not be synchronized since it cannot be provably stale. */ public Location location() { validateStackFrame(); return location; } /** * Return the thread holding the frame. * Need not be synchronized since it cannot be provably stale. */ public ThreadReference thread() { validateStackFrame(); return thread; } public boolean equals(Object obj) { if ((obj != null) && (obj instanceof StackFrameImpl)) { StackFrameImpl other = (StackFrameImpl)obj; return (id == other.id) && (thread().equals(other.thread())) && (location().equals(other.location())) && super.equals(obj); } else { return false; } } public int hashCode() { return (thread().hashCode() << 4) + ((int)id); } public ObjectReference thisObject() { validateStackFrame(); MethodImpl currentMethod = (MethodImpl)location.method(); if (currentMethod.isStatic() || currentMethod.isNative()) { return null; } else { if (thisObject == null) { PacketStream ps; /* protect against defunct frame id */ synchronized (vm.state()) { validateStackFrame(); ps = JDWP.StackFrame.ThisObject. enqueueCommand(vm, thread, id); } /* actually get it, now that order is guaranteed */ try { thisObject = JDWP.StackFrame.ThisObject. waitForReply(vm, ps).objectThis; } catch (JDWPException exc) { switch (exc.errorCode()) { case JDWP.Error.INVALID_FRAMEID: case JDWP.Error.THREAD_NOT_SUSPENDED: case JDWP.Error.INVALID_THREAD: throw new InvalidStackFrameException(); default: throw exc.toJDIException(); } } } } return thisObject; } /** * Build the visible variable map. * Need not be synchronized since it cannot be provably stale. */ private void createVisibleVariables() throws AbsentInformationException { if (visibleVariables == null) { List<LocalVariable> allVariables = location.method().variables(); Map<String, LocalVariable> map = new HashMap<String, LocalVariable>(allVariables.size()); for (LocalVariable variable : allVariables) { String name = variable.name(); if (variable.isVisible(this)) { LocalVariable existing = map.get(name); if ((existing == null) || ((LocalVariableImpl)variable).hides(existing)) { map.put(name, variable); } } } visibleVariables = map; } } /** * Return the list of visible variable in the frame. * Need not be synchronized since it cannot be provably stale. */ public List<LocalVariable> visibleVariables() throws AbsentInformationException { validateStackFrame(); createVisibleVariables(); List<LocalVariable> mapAsList = new ArrayList<LocalVariable>(visibleVariables.values()); Collections.sort(mapAsList); return mapAsList; } /** * Return a particular variable in the frame. * Need not be synchronized since it cannot be provably stale. */ public LocalVariable visibleVariableByName(String name) throws AbsentInformationException { validateStackFrame(); createVisibleVariables(); return visibleVariables.get(name); } public Value getValue(LocalVariable variable) { List<LocalVariable> list = new ArrayList<LocalVariable>(1); list.add(variable); return getValues(list).get(variable); } public Map<LocalVariable, Value> getValues(List<? extends LocalVariable> variables) { validateStackFrame(); validateMirrors(variables); int count = variables.size(); JDWP.StackFrame.GetValues.SlotInfo[] slots = new JDWP.StackFrame.GetValues.SlotInfo[count]; for (int i=0; i<count; ++i) { LocalVariableImpl variable = (LocalVariableImpl)variables.get(i); if (!variable.isVisible(this)) { throw new IllegalArgumentException(variable.name() + " is not valid at this frame location"); } slots[i] = new JDWP.StackFrame.GetValues.SlotInfo(variable.slot(), (byte)variable.signature().charAt(0)); } PacketStream ps; /* protect against defunct frame id */ synchronized (vm.state()) { validateStackFrame(); ps = JDWP.StackFrame.GetValues.enqueueCommand(vm, thread, id, slots); } /* actually get it, now that order is guaranteed */ ValueImpl[] values; try { values = JDWP.StackFrame.GetValues.waitForReply(vm, ps).values; } catch (JDWPException exc) { switch (exc.errorCode()) { case JDWP.Error.INVALID_FRAMEID: case JDWP.Error.THREAD_NOT_SUSPENDED: case JDWP.Error.INVALID_THREAD: throw new InvalidStackFrameException(); default: throw exc.toJDIException(); } } if (count != values.length) { throw new InternalException( "Wrong number of values returned from target VM"); } Map<LocalVariable, Value> map = new HashMap<LocalVariable, Value>(count); for (int i=0; i<count; ++i) { LocalVariableImpl variable = (LocalVariableImpl)variables.get(i); map.put(variable, values[i]); } return map; } public void setValue(LocalVariable variableIntf, Value valueIntf) throws InvalidTypeException, ClassNotLoadedException { validateStackFrame(); validateMirror(variableIntf); validateMirrorOrNull(valueIntf); LocalVariableImpl variable = (LocalVariableImpl)variableIntf; ValueImpl value = (ValueImpl)valueIntf; if (!variable.isVisible(this)) { throw new IllegalArgumentException(variable.name() + " is not valid at this frame location"); } try { // Validate and convert value if necessary value = ValueImpl.prepareForAssignment(value, variable); JDWP.StackFrame.SetValues.SlotInfo[] slotVals = new JDWP.StackFrame.SetValues.SlotInfo[1]; slotVals[0] = new JDWP.StackFrame.SetValues. SlotInfo(variable.slot(), value); PacketStream ps; /* protect against defunct frame id */ synchronized (vm.state()) { validateStackFrame(); ps = JDWP.StackFrame.SetValues. enqueueCommand(vm, thread, id, slotVals); } /* actually set it, now that order is guaranteed */ try { JDWP.StackFrame.SetValues.waitForReply(vm, ps); } catch (JDWPException exc) { switch (exc.errorCode()) { case JDWP.Error.INVALID_FRAMEID: case JDWP.Error.THREAD_NOT_SUSPENDED: case JDWP.Error.INVALID_THREAD: throw new InvalidStackFrameException(); default: throw exc.toJDIException(); } } } catch (ClassNotLoadedException e) { /* * Since we got this exception, * the variable type must be a reference type. The value * we're trying to set is null, but if the variable's * class has not yet been loaded through the enclosing * class loader, then setting to null is essentially a * no-op, and we should allow it without an exception. */ if (value != null) { throw e; } } } public List<Value> getArgumentValues() { validateStackFrame(); MethodImpl mmm = (MethodImpl)location.method(); List<String> argSigs = mmm.argumentSignatures(); int count = argSigs.size(); JDWP.StackFrame.GetValues.SlotInfo[] slots = new JDWP.StackFrame.GetValues.SlotInfo[count]; int slot; if (mmm.isStatic()) { slot = 0; } else { slot = 1; } for (int ii = 0; ii < count; ++ii) { char sigChar = argSigs.get(ii).charAt(0); slots[ii] = new JDWP.StackFrame.GetValues.SlotInfo(slot++,(byte)sigChar); if (sigChar == 'J' || sigChar == 'D') { slot++; } } PacketStream ps; /* protect against defunct frame id */ synchronized (vm.state()) { validateStackFrame(); ps = JDWP.StackFrame.GetValues.enqueueCommand(vm, thread, id, slots); } ValueImpl[] values; try { values = JDWP.StackFrame.GetValues.waitForReply(vm, ps).values; } catch (JDWPException exc) { switch (exc.errorCode()) { case JDWP.Error.INVALID_FRAMEID: case JDWP.Error.THREAD_NOT_SUSPENDED: case JDWP.Error.INVALID_THREAD: throw new InvalidStackFrameException(); default: throw exc.toJDIException(); } } if (count != values.length) { throw new InternalException( "Wrong number of values returned from target VM"); } return Arrays.asList((Value[])values); } void pop() throws IncompatibleThreadStateException { validateStackFrame(); // flush caches and disable caching until command completion CommandSender sender = new CommandSender() { public PacketStream send() { return JDWP.StackFrame.PopFrames.enqueueCommand(vm, thread, id); } }; try { PacketStream stream = thread.sendResumingCommand(sender); JDWP.StackFrame.PopFrames.waitForReply(vm, stream); } catch (JDWPException exc) { switch (exc.errorCode()) { case JDWP.Error.THREAD_NOT_SUSPENDED: throw new IncompatibleThreadStateException( "Thread not current or suspended"); case JDWP.Error.INVALID_THREAD: /* zombie */ throw new IncompatibleThreadStateException("zombie"); case JDWP.Error.NO_MORE_FRAMES: throw new InvalidStackFrameException( "No more frames on the stack"); default: throw exc.toJDIException(); } } // enable caching - suspended again vm.state().freeze(); } public String toString() { return location.toString() + " in thread " + thread.toString(); } }