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