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