1 /*
2  * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.jdi;
27 
28 import com.sun.jdi.*;
29 
30 import java.util.List;
31 import java.util.Map;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.Collections;
37 
38 public class StackFrameImpl extends MirrorImpl
39                             implements StackFrame, ThreadListener
40 {
41     /* Once false, frame should not be used.
42      * access synchronized on (vm.state())
43      */
44     private boolean isValid = true;
45 
46     private final ThreadReferenceImpl thread;
47     private final long id;
48     private final Location location;
49     private Map<String, LocalVariable> visibleVariables =  null;
50     private ObjectReference thisObject = null;
51 
StackFrameImpl(VirtualMachine vm, ThreadReferenceImpl thread, long id, Location location)52     StackFrameImpl(VirtualMachine vm, ThreadReferenceImpl thread,
53                    long id, Location location) {
54         super(vm);
55         this.thread = thread;
56         this.id = id;
57         this.location = location;
58         thread.addListener(this);
59     }
60 
61     /*
62      * ThreadListener implementation
63      * Must be synchronized since we must protect against
64      * sending defunct (isValid == false) stack ids to the back-end.
65      */
threadResumable(ThreadAction action)66     public boolean threadResumable(ThreadAction action) {
67         synchronized (vm.state()) {
68             if (isValid) {
69                 isValid = false;
70                 return false;   /* remove this stack frame as a listener */
71             } else {
72                 throw new InternalException(
73                                   "Invalid stack frame thread listener");
74             }
75         }
76     }
77 
validateStackFrame()78     void validateStackFrame() {
79         if (!isValid) {
80             throw new InvalidStackFrameException("Thread has been resumed");
81         }
82     }
83 
84     /**
85      * Return the frame location.
86      * Need not be synchronized since it cannot be provably stale.
87      */
location()88     public Location location() {
89         validateStackFrame();
90         return location;
91     }
92 
93     /**
94      * Return the thread holding the frame.
95      * Need not be synchronized since it cannot be provably stale.
96      */
thread()97     public ThreadReference thread() {
98         validateStackFrame();
99         return thread;
100     }
101 
equals(Object obj)102     public boolean equals(Object obj) {
103         if ((obj != null) && (obj instanceof StackFrameImpl)) {
104             StackFrameImpl other = (StackFrameImpl)obj;
105             return (id == other.id) &&
106                    (thread().equals(other.thread())) &&
107                    (location().equals(other.location())) &&
108                     super.equals(obj);
109         } else {
110             return false;
111         }
112     }
113 
hashCode()114     public int hashCode() {
115         return (thread().hashCode() << 4) + ((int)id);
116     }
117 
thisObject()118     public ObjectReference thisObject() {
119         validateStackFrame();
120         MethodImpl currentMethod = (MethodImpl)location.method();
121         if (currentMethod.isStatic() || currentMethod.isNative()) {
122             return null;
123         } else {
124             if (thisObject == null) {
125                 PacketStream ps;
126 
127                 /* protect against defunct frame id */
128                 synchronized (vm.state()) {
129                     validateStackFrame();
130                     ps = JDWP.StackFrame.ThisObject.
131                                       enqueueCommand(vm, thread, id);
132                 }
133 
134                 /* actually get it, now that order is guaranteed */
135                 try {
136                     thisObject = JDWP.StackFrame.ThisObject.
137                                       waitForReply(vm, ps).objectThis;
138                 } catch (JDWPException exc) {
139                     switch (exc.errorCode()) {
140                     case JDWP.Error.INVALID_FRAMEID:
141                     case JDWP.Error.THREAD_NOT_SUSPENDED:
142                     case JDWP.Error.INVALID_THREAD:
143                         throw new InvalidStackFrameException();
144                     default:
145                         throw exc.toJDIException();
146                     }
147                 }
148             }
149         }
150         return thisObject;
151     }
152 
153     /**
154      * Build the visible variable map.
155      * Need not be synchronized since it cannot be provably stale.
156      */
createVisibleVariables()157     private void createVisibleVariables() throws AbsentInformationException {
158         if (visibleVariables == null) {
159             List<LocalVariable> allVariables = location.method().variables();
160             Map<String, LocalVariable> map = new HashMap<String, LocalVariable>(allVariables.size());
161 
162             for (LocalVariable variable : allVariables) {
163                 String name = variable.name();
164                 if (variable.isVisible(this)) {
165                     LocalVariable existing = map.get(name);
166                     if ((existing == null) ||
167                         ((LocalVariableImpl)variable).hides(existing)) {
168                         map.put(name, variable);
169                     }
170                 }
171             }
172             visibleVariables = map;
173         }
174     }
175 
176     /**
177      * Return the list of visible variable in the frame.
178      * Need not be synchronized since it cannot be provably stale.
179      */
visibleVariables()180     public List<LocalVariable> visibleVariables() throws AbsentInformationException {
181         validateStackFrame();
182         createVisibleVariables();
183         List<LocalVariable> mapAsList = new ArrayList<LocalVariable>(visibleVariables.values());
184         Collections.sort(mapAsList);
185         return mapAsList;
186     }
187 
188     /**
189      * Return a particular variable in the frame.
190      * Need not be synchronized since it cannot be provably stale.
191      */
visibleVariableByName(String name)192     public LocalVariable visibleVariableByName(String name) throws AbsentInformationException  {
193         validateStackFrame();
194         createVisibleVariables();
195         return visibleVariables.get(name);
196     }
197 
getValue(LocalVariable variable)198     public Value getValue(LocalVariable variable) {
199         List<LocalVariable> list = new ArrayList<LocalVariable>(1);
200         list.add(variable);
201         return getValues(list).get(variable);
202     }
203 
getValues(List<? extends LocalVariable> variables)204     public Map<LocalVariable, Value> getValues(List<? extends LocalVariable> variables) {
205         validateStackFrame();
206         validateMirrors(variables);
207 
208         int count = variables.size();
209         JDWP.StackFrame.GetValues.SlotInfo[] slots =
210                            new JDWP.StackFrame.GetValues.SlotInfo[count];
211 
212         for (int i=0; i<count; ++i) {
213             LocalVariableImpl variable = (LocalVariableImpl)variables.get(i);
214             if (!variable.isVisible(this)) {
215                 throw new IllegalArgumentException(variable.name() +
216                                  " is not valid at this frame location");
217             }
218             slots[i] = new JDWP.StackFrame.GetValues.SlotInfo(variable.slot(),
219                                       (byte)variable.signature().charAt(0));
220         }
221 
222         PacketStream ps;
223 
224         /* protect against defunct frame id */
225         synchronized (vm.state()) {
226             validateStackFrame();
227             ps = JDWP.StackFrame.GetValues.enqueueCommand(vm, thread, id, slots);
228         }
229 
230         /* actually get it, now that order is guaranteed */
231         ValueImpl[] values;
232         try {
233             values = JDWP.StackFrame.GetValues.waitForReply(vm, ps).values;
234         } catch (JDWPException exc) {
235             switch (exc.errorCode()) {
236                 case JDWP.Error.INVALID_FRAMEID:
237                 case JDWP.Error.THREAD_NOT_SUSPENDED:
238                 case JDWP.Error.INVALID_THREAD:
239                     throw new InvalidStackFrameException();
240                 default:
241                     throw exc.toJDIException();
242             }
243         }
244 
245         if (count != values.length) {
246             throw new InternalException(
247                       "Wrong number of values returned from target VM");
248         }
249         Map<LocalVariable, Value> map = new HashMap<LocalVariable, Value>(count);
250         for (int i=0; i<count; ++i) {
251             LocalVariableImpl variable = (LocalVariableImpl)variables.get(i);
252             map.put(variable, values[i]);
253         }
254         return map;
255     }
256 
setValue(LocalVariable variableIntf, Value valueIntf)257     public void setValue(LocalVariable variableIntf, Value valueIntf)
258         throws InvalidTypeException, ClassNotLoadedException {
259 
260         validateStackFrame();
261         validateMirror(variableIntf);
262         validateMirrorOrNull(valueIntf);
263 
264         LocalVariableImpl variable = (LocalVariableImpl)variableIntf;
265         ValueImpl value = (ValueImpl)valueIntf;
266 
267         if (!variable.isVisible(this)) {
268             throw new IllegalArgumentException(variable.name() +
269                              " is not valid at this frame location");
270         }
271 
272         try {
273             // Validate and convert value if necessary
274             value = ValueImpl.prepareForAssignment(value, variable);
275 
276             JDWP.StackFrame.SetValues.SlotInfo[] slotVals =
277                 new JDWP.StackFrame.SetValues.SlotInfo[1];
278             slotVals[0] = new JDWP.StackFrame.SetValues.
279                                        SlotInfo(variable.slot(), value);
280 
281             PacketStream ps;
282 
283             /* protect against defunct frame id */
284             synchronized (vm.state()) {
285                 validateStackFrame();
286                 ps = JDWP.StackFrame.SetValues.
287                                      enqueueCommand(vm, thread, id, slotVals);
288             }
289 
290             /* actually set it, now that order is guaranteed */
291             try {
292                 JDWP.StackFrame.SetValues.waitForReply(vm, ps);
293             } catch (JDWPException exc) {
294                 switch (exc.errorCode()) {
295                 case JDWP.Error.INVALID_FRAMEID:
296                 case JDWP.Error.THREAD_NOT_SUSPENDED:
297                 case JDWP.Error.INVALID_THREAD:
298                     throw new InvalidStackFrameException();
299                 default:
300                     throw exc.toJDIException();
301                 }
302             }
303         } catch (ClassNotLoadedException e) {
304             /*
305              * Since we got this exception,
306              * the variable type must be a reference type. The value
307              * we're trying to set is null, but if the variable's
308              * class has not yet been loaded through the enclosing
309              * class loader, then setting to null is essentially a
310              * no-op, and we should allow it without an exception.
311              */
312             if (value != null) {
313                 throw e;
314             }
315         }
316     }
317 
getArgumentValues()318     public List<Value> getArgumentValues() {
319         validateStackFrame();
320         MethodImpl mmm = (MethodImpl)location.method();
321         List<String> argSigs = mmm.argumentSignatures();
322         int count = argSigs.size();
323         JDWP.StackFrame.GetValues.SlotInfo[] slots =
324                            new JDWP.StackFrame.GetValues.SlotInfo[count];
325 
326         int slot;
327         if (mmm.isStatic()) {
328             slot = 0;
329         } else {
330             slot = 1;
331         }
332         for (int ii = 0; ii < count; ++ii) {
333             char sigChar = argSigs.get(ii).charAt(0);
334             slots[ii] = new JDWP.StackFrame.GetValues.SlotInfo(slot++,(byte)sigChar);
335             if (sigChar == 'J' || sigChar == 'D') {
336                 slot++;
337             }
338         }
339 
340         PacketStream ps;
341 
342         /* protect against defunct frame id */
343         synchronized (vm.state()) {
344             validateStackFrame();
345             ps = JDWP.StackFrame.GetValues.enqueueCommand(vm, thread, id, slots);
346         }
347 
348         ValueImpl[] values;
349         try {
350             values = JDWP.StackFrame.GetValues.waitForReply(vm, ps).values;
351         } catch (JDWPException exc) {
352             switch (exc.errorCode()) {
353                 case JDWP.Error.INVALID_FRAMEID:
354                 case JDWP.Error.THREAD_NOT_SUSPENDED:
355                 case JDWP.Error.INVALID_THREAD:
356                     throw new InvalidStackFrameException();
357                 default:
358                     throw exc.toJDIException();
359             }
360         }
361 
362         if (count != values.length) {
363             throw new InternalException(
364                       "Wrong number of values returned from target VM");
365         }
366         return Arrays.asList((Value[])values);
367     }
368 
pop()369     void pop() throws IncompatibleThreadStateException {
370         validateStackFrame();
371         // flush caches and disable caching until command completion
372         CommandSender sender =
373             new CommandSender() {
374                 public PacketStream send() {
375                     return JDWP.StackFrame.PopFrames.enqueueCommand(vm,
376                                  thread, id);
377                 }
378         };
379         try {
380             PacketStream stream = thread.sendResumingCommand(sender);
381             JDWP.StackFrame.PopFrames.waitForReply(vm, stream);
382         } catch (JDWPException exc) {
383             switch (exc.errorCode()) {
384             case JDWP.Error.THREAD_NOT_SUSPENDED:
385                 throw new IncompatibleThreadStateException(
386                          "Thread not current or suspended");
387             case JDWP.Error.INVALID_THREAD:   /* zombie */
388                 throw new IncompatibleThreadStateException("zombie");
389             case JDWP.Error.NO_MORE_FRAMES:
390                 throw new InvalidStackFrameException(
391                          "No more frames on the stack");
392             default:
393                 throw exc.toJDIException();
394             }
395         }
396 
397         // enable caching - suspended again
398         vm.state().freeze();
399     }
400 
toString()401     public String toString() {
402        return location.toString() + " in thread " + thread.toString();
403     }
404 }
405