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
26package com.sun.tools.jdi;
27
28import com.sun.jdi.*;
29
30import java.util.List;
31import java.util.Map;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.HashMap;
35import java.util.Iterator;
36import java.util.Collections;
37
38public 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
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     */
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
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     */
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     */
97    public ThreadReference thread() {
98        validateStackFrame();
99        return thread;
100    }
101
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
114    public int hashCode() {
115        return (thread().hashCode() << 4) + ((int)id);
116    }
117
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     */
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     */
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     */
192    public LocalVariable visibleVariableByName(String name) throws AbsentInformationException  {
193        validateStackFrame();
194        createVisibleVariables();
195        return visibleVariables.get(name);
196    }
197
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
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
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
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
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
401    public String toString() {
402       return location.toString() + " in thread " + thread.toString();
403    }
404}
405