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 26 package com.sun.tools.jdi; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 34 import com.sun.jdi.ClassNotLoadedException; 35 import com.sun.jdi.ClassType; 36 import com.sun.jdi.Field; 37 import com.sun.jdi.IncompatibleThreadStateException; 38 import com.sun.jdi.InterfaceType; 39 import com.sun.jdi.InternalException; 40 import com.sun.jdi.InvalidTypeException; 41 import com.sun.jdi.InvocationException; 42 import com.sun.jdi.Method; 43 import com.sun.jdi.ObjectReference; 44 import com.sun.jdi.ReferenceType; 45 import com.sun.jdi.ThreadReference; 46 import com.sun.jdi.Type; 47 import com.sun.jdi.Value; 48 import com.sun.jdi.VirtualMachine; 49 50 public class ObjectReferenceImpl extends ValueImpl 51 implements ObjectReference, VMListener 52 { 53 protected long ref; 54 private ReferenceType type = null; 55 private int gcDisableCount = 0; 56 boolean addedListener = false; 57 58 // This is cached only while the VM is suspended 59 protected static class Cache { 60 JDWP.ObjectReference.MonitorInfo monitorInfo = null; 61 } 62 63 private static final Cache noInitCache = new Cache(); 64 private static final Cache markerCache = new Cache(); 65 private Cache cache = noInitCache; 66 67 private void disableCache() { 68 synchronized (vm.state()) { 69 cache = null; 70 } 71 } 72 73 private void enableCache() { 74 synchronized (vm.state()) { 75 cache = markerCache; 76 } 77 } 78 79 private boolean isInlineType() { 80 return referenceType().signature().startsWith("Q"); 81 } 82 83 // Override in subclasses 84 protected Cache newCache() { 85 return new Cache(); 86 } 87 88 protected Cache getCache() { 89 synchronized (vm.state()) { 90 if (cache == noInitCache) { 91 if (vm.state().isSuspended()) { 92 // Set cache now, otherwise newly created objects are 93 // not cached until resuspend 94 enableCache(); 95 } else { 96 disableCache(); 97 } 98 } 99 if (cache == markerCache) { 100 cache = newCache(); 101 } 102 return cache; 103 } 104 } 105 106 // Return the ClassTypeImpl upon which to invoke a method. 107 // By default it is our very own referenceType() but subclasses 108 // can override. 109 protected ClassTypeImpl invokableReferenceType(Method method) { 110 return (ClassTypeImpl)referenceType(); 111 } 112 113 ObjectReferenceImpl(VirtualMachine aVm,long aRef) { 114 super(aVm); 115 116 ref = aRef; 117 } 118 119 protected String description() { 120 return "ObjectReference " + uniqueID(); 121 } 122 123 /* 124 * VMListener implementation 125 */ 126 public boolean vmSuspended(VMAction action) { 127 enableCache(); 128 return true; 129 } 130 131 public boolean vmNotSuspended(VMAction action) { 132 // make sure that cache and listener management are synchronized 133 synchronized (vm.state()) { 134 if (cache != null && (vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 135 vm.printTrace("Clearing temporary cache for " + description()); 136 } 137 disableCache(); 138 if (addedListener) { 139 /* 140 * If a listener was added (i.e. this is not a 141 * ObjectReference that adds a listener on startup), 142 * remove it here. 143 */ 144 addedListener = false; 145 return false; // false says remove 146 } else { 147 return true; 148 } 149 } 150 } 151 152 private boolean isSubtituable(ObjectReferenceImpl other) { 153 if (referenceType() != other.referenceType()) return false; 154 List<Field> fields = referenceType().fields(); 155 for (Field f : fields) { 156 if (f.isStatic()) { 157 fields.remove(f); 158 } 159 } 160 Map<Field,Value> thisFields = getValues(fields); 161 Map<Field,Value> otherFields = other.getValues(fields); 162 for (Field f : fields) { 163 if (!thisFields.get(f).equals(otherFields.get(f))) return false; 164 } 165 return true; 166 } 167 168 public boolean equals(Object obj) { 169 if ((obj != null) && (obj instanceof ObjectReferenceImpl)) { 170 ObjectReferenceImpl other = (ObjectReferenceImpl) obj; 171 if (isInlineType()) { 172 return isSubtituable(other); 173 174 } else { 175 return (ref() == other.ref()) && 176 super.equals(obj); 177 } 178 } else { 179 return false; 180 } 181 } 182 183 public int hashCode() { 184 return(int)ref(); 185 } 186 187 public Type type() { 188 return referenceType(); 189 } 190 191 public ReferenceType referenceType() { 192 if (type == null) { 193 try { 194 JDWP.ObjectReference.ReferenceType rtinfo = 195 JDWP.ObjectReference.ReferenceType.process(vm, this); 196 type = vm.referenceType(rtinfo.typeID, 197 rtinfo.refTypeTag); 198 } catch (JDWPException exc) { 199 throw exc.toJDIException(); 200 } 201 } 202 return type; 203 } 204 205 public Value getValue(Field sig) { 206 List<Field> list = new ArrayList<>(1); 207 list.add(sig); 208 Map<Field, Value> map = getValues(list); 209 return map.get(sig); 210 } 211 212 public Map<Field,Value> getValues(List<? extends Field> theFields) { 213 validateMirrors(theFields); 214 215 List<Field> staticFields = new ArrayList<>(0); 216 int size = theFields.size(); 217 List<Field> instanceFields = new ArrayList<>(size); 218 219 for (int i = 0; i < size; i++) { 220 Field field = theFields.get(i); 221 222 // Make sure the field is valid 223 ((ReferenceTypeImpl)referenceType()).validateFieldAccess(field); 224 225 // FIX ME! We need to do some sanity checking 226 // here; make sure the field belongs to this 227 // object. 228 if (field.isStatic()) 229 staticFields.add(field); 230 else { 231 instanceFields.add(field); 232 } 233 } 234 235 Map<Field, Value> map; 236 if (staticFields.size() > 0) { 237 map = referenceType().getValues(staticFields); 238 } else { 239 map = new HashMap<Field, Value>(size); 240 } 241 242 size = instanceFields.size(); 243 244 JDWP.ObjectReference.GetValues.Field[] queryFields = 245 new JDWP.ObjectReference.GetValues.Field[size]; 246 for (int i=0; i<size; i++) { 247 FieldImpl field = (FieldImpl)instanceFields.get(i);/* thanks OTI */ 248 queryFields[i] = new JDWP.ObjectReference.GetValues.Field( 249 field.ref()); 250 } 251 ValueImpl[] values; 252 try { 253 values = JDWP.ObjectReference.GetValues. 254 process(vm, this, queryFields).values; 255 } catch (JDWPException exc) { 256 throw exc.toJDIException(); 257 } 258 259 if (size != values.length) { 260 throw new InternalException( 261 "Wrong number of values returned from target VM"); 262 } 263 for (int i=0; i<size; i++) { 264 FieldImpl field = (FieldImpl)instanceFields.get(i); 265 map.put(field, values[i]); 266 } 267 268 return map; 269 } 270 271 public void setValue(Field field, Value value) 272 throws InvalidTypeException, ClassNotLoadedException { 273 274 validateMirror(field); 275 validateMirrorOrNull(value); 276 277 // Make sure the field is valid 278 ((ReferenceTypeImpl)referenceType()).validateFieldSet(field); 279 280 if (field.isStatic()) { 281 ReferenceType type = referenceType(); 282 if (type instanceof ClassType) { 283 ((ClassType)type).setValue(field, value); 284 return; 285 } else { 286 throw new IllegalArgumentException( 287 "Invalid type for static field set"); 288 } 289 } 290 291 try { 292 JDWP.ObjectReference.SetValues.FieldValue[] fvals = 293 new JDWP.ObjectReference.SetValues.FieldValue[1]; 294 fvals[0] = new JDWP.ObjectReference.SetValues.FieldValue( 295 ((FieldImpl)field).ref(), 296 // Validate and convert if necessary 297 ValueImpl.prepareForAssignment(value, 298 (FieldImpl)field)); 299 try { 300 JDWP.ObjectReference.SetValues.process(vm, this, fvals); 301 } catch (JDWPException exc) { 302 throw exc.toJDIException(); 303 } 304 } catch (ClassNotLoadedException e) { 305 /* 306 * Since we got this exception, 307 * the field type must be a reference type. The value 308 * we're trying to set is null, but if the field's 309 * class has not yet been loaded through the enclosing 310 * class loader, then setting to null is essentially a 311 * no-op, and we should allow it without an exception. 312 */ 313 if (value != null) { 314 throw e; 315 } 316 } 317 } 318 319 void validateMethodInvocation(Method method, int options) 320 throws InvalidTypeException, 321 InvocationException { 322 /* 323 * Method must be in this object's class, a superclass, or 324 * implemented interface 325 */ 326 ReferenceTypeImpl declType = (ReferenceTypeImpl)method.declaringType(); 327 328 if (!declType.isAssignableFrom(this)) { 329 throw new IllegalArgumentException("Invalid method"); 330 } 331 332 if (declType instanceof ClassTypeImpl) { 333 validateClassMethodInvocation(method, options); 334 } else if (declType instanceof InterfaceTypeImpl) { 335 validateIfaceMethodInvocation(method, options); 336 } else { 337 throw new InvalidTypeException(); 338 } 339 } 340 341 void validateClassMethodInvocation(Method method, int options) 342 throws InvalidTypeException, 343 InvocationException { 344 /* 345 * Method must be a non-constructor 346 */ 347 if (method.isConstructor()) { 348 throw new IllegalArgumentException("Cannot invoke constructor"); 349 } 350 351 /* 352 * For nonvirtual invokes, method must have a body 353 */ 354 if (isNonVirtual(options)) { 355 if (method.isAbstract()) { 356 throw new IllegalArgumentException("Abstract method"); 357 } 358 } 359 } 360 361 void validateIfaceMethodInvocation(Method method, int options) 362 throws InvalidTypeException, 363 InvocationException { 364 /* 365 * For nonvirtual invokes, method must have a body 366 */ 367 if (isNonVirtual(options)) { 368 if (method.isAbstract()) { 369 throw new IllegalArgumentException("Abstract method"); 370 } 371 } 372 } 373 374 PacketStream sendInvokeCommand(final ThreadReferenceImpl thread, 375 final ClassTypeImpl refType, 376 final MethodImpl method, 377 final ValueImpl[] args, 378 final int options) { 379 CommandSender sender = 380 new CommandSender() { 381 public PacketStream send() { 382 return JDWP.ObjectReference.InvokeMethod.enqueueCommand( 383 vm, ObjectReferenceImpl.this, 384 thread, refType, 385 method.ref(), args, options); 386 } 387 }; 388 389 PacketStream stream; 390 if ((options & INVOKE_SINGLE_THREADED) != 0) { 391 stream = thread.sendResumingCommand(sender); 392 } else { 393 stream = vm.sendResumingCommand(sender); 394 } 395 return stream; 396 } 397 398 public Value invokeMethod(ThreadReference threadIntf, Method methodIntf, 399 List<? extends Value> origArguments, int options) 400 throws InvalidTypeException, 401 IncompatibleThreadStateException, 402 InvocationException, 403 ClassNotLoadedException { 404 405 validateMirror(threadIntf); 406 validateMirror(methodIntf); 407 validateMirrorsOrNulls(origArguments); 408 409 MethodImpl method = (MethodImpl)methodIntf; 410 ThreadReferenceImpl thread = (ThreadReferenceImpl)threadIntf; 411 412 if (method.isStatic()) { 413 if (referenceType() instanceof InterfaceType) { 414 InterfaceType type = (InterfaceType)referenceType(); 415 return type.invokeMethod(thread, method, origArguments, options); 416 } else if (referenceType() instanceof ClassType) { 417 ClassType type = (ClassType)referenceType(); 418 return type.invokeMethod(thread, method, origArguments, options); 419 } else { 420 throw new IllegalArgumentException("Invalid type for static method invocation"); 421 } 422 } 423 424 validateMethodInvocation(method, options); 425 426 List<Value> arguments = method.validateAndPrepareArgumentsForInvoke( 427 origArguments); 428 429 ValueImpl[] args = arguments.toArray(new ValueImpl[0]); 430 JDWP.ObjectReference.InvokeMethod ret; 431 try { 432 PacketStream stream = 433 sendInvokeCommand(thread, invokableReferenceType(method), 434 method, args, options); 435 ret = JDWP.ObjectReference.InvokeMethod.waitForReply(vm, stream); 436 } catch (JDWPException exc) { 437 if (exc.errorCode() == JDWP.Error.INVALID_THREAD) { 438 throw new IncompatibleThreadStateException(); 439 } else { 440 throw exc.toJDIException(); 441 } 442 } 443 444 /* 445 * There is an implict VM-wide suspend at the conclusion 446 * of a normal (non-single-threaded) method invoke 447 */ 448 if ((options & INVOKE_SINGLE_THREADED) == 0) { 449 vm.notifySuspend(); 450 } 451 452 if (ret.exception != null) { 453 throw new InvocationException(ret.exception); 454 } else { 455 return ret.returnValue; 456 } 457 } 458 459 /* leave synchronized to keep count accurate */ 460 public synchronized void disableCollection() { 461 if (gcDisableCount == 0) { 462 try { 463 JDWP.ObjectReference.DisableCollection.process(vm, this); 464 } catch (JDWPException exc) { 465 throw exc.toJDIException(); 466 } 467 } 468 gcDisableCount++; 469 } 470 471 /* leave synchronized to keep count accurate */ 472 public synchronized void enableCollection() { 473 gcDisableCount--; 474 475 if (gcDisableCount == 0) { 476 try { 477 JDWP.ObjectReference.EnableCollection.process(vm, this); 478 } catch (JDWPException exc) { 479 // If already collected, no harm done, no exception 480 if (exc.errorCode() != JDWP.Error.INVALID_OBJECT) { 481 throw exc.toJDIException(); 482 } 483 return; 484 } 485 } 486 } 487 488 public boolean isCollected() { 489 try { 490 return JDWP.ObjectReference.IsCollected.process(vm, this). 491 isCollected; 492 } catch (JDWPException exc) { 493 throw exc.toJDIException(); 494 } 495 } 496 497 public long uniqueID() { 498 if (isInlineType()) { 499 throw new UnsupportedOperationException("Inline types cannot have unique IDs"); 500 } 501 return ref(); 502 } 503 504 JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo() 505 throws IncompatibleThreadStateException { 506 JDWP.ObjectReference.MonitorInfo info = null; 507 try { 508 Cache local; 509 510 // getCache() and addlistener() must be synchronized 511 // so that no events are lost. 512 synchronized (vm.state()) { 513 local = getCache(); 514 515 if (local != null) { 516 info = local.monitorInfo; 517 518 // Check if there will be something to cache 519 // and there is not already a listener 520 if (info == null && !vm.state().hasListener(this)) { 521 /* For other, less numerous objects, this is done 522 * in the constructor. Since there can be many 523 * ObjectReferences, the VM listener is installed 524 * and removed as needed. 525 * Listener must be installed before process() 526 */ 527 vm.state().addListener(this); 528 addedListener = true; 529 } 530 } 531 } 532 if (info == null) { 533 info = JDWP.ObjectReference.MonitorInfo.process(vm, this); 534 if (local != null) { 535 local.monitorInfo = info; 536 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 537 vm.printTrace("ObjectReference " + uniqueID() + 538 " temporarily caching monitor info"); 539 } 540 } 541 } 542 } catch (JDWPException exc) { 543 if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) { 544 throw new IncompatibleThreadStateException(); 545 } else { 546 throw exc.toJDIException(); 547 } 548 } 549 return info; 550 } 551 552 public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException { 553 return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters); 554 } 555 556 public ThreadReference owningThread() throws IncompatibleThreadStateException { 557 return jdwpMonitorInfo().owner; 558 } 559 560 public int entryCount() throws IncompatibleThreadStateException { 561 return jdwpMonitorInfo().entryCount; 562 } 563 564 565 public List<ObjectReference> referringObjects(long maxReferrers) { 566 if (!vm.canGetInstanceInfo()) { 567 throw new UnsupportedOperationException( 568 "target does not support getting referring objects"); 569 } 570 571 if (maxReferrers < 0) { 572 throw new IllegalArgumentException("maxReferrers is less than zero: " 573 + maxReferrers); 574 } 575 576 int intMax = (maxReferrers > Integer.MAX_VALUE)? 577 Integer.MAX_VALUE: (int)maxReferrers; 578 // JDWP can't currently handle more than this (in mustang) 579 580 try { 581 return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects. 582 process(vm, this, intMax).referringObjects); 583 } catch (JDWPException exc) { 584 throw exc.toJDIException(); 585 } 586 } 587 588 long ref() { 589 return ref; 590 } 591 592 boolean isClassObject() { 593 /* 594 * Don't need to worry about subclasses since java.lang.Class is final. 595 */ 596 return referenceType().name().equals("java.lang.Class"); 597 } 598 599 ValueImpl prepareForAssignmentTo(ValueContainer destination) 600 throws InvalidTypeException, 601 ClassNotLoadedException { 602 603 validateAssignment(destination); 604 return this; // conversion never necessary 605 } 606 607 void validateAssignment(ValueContainer destination) 608 throws InvalidTypeException, ClassNotLoadedException { 609 610 /* 611 * Do these simpler checks before attempting a query of the destination's 612 * type which might cause a confusing ClassNotLoadedException if 613 * the destination is primitive or an array. 614 */ 615 /* 616 * TO DO: Centralize JNI signature knowledge 617 */ 618 if (destination.signature().length() == 1) { 619 throw new InvalidTypeException("Can't assign object value to primitive"); 620 } 621 if ((destination.signature().charAt(0) == '[') && 622 (type().signature().charAt(0) != '[')) { 623 throw new InvalidTypeException("Can't assign non-array value to an array"); 624 } 625 if ("void".equals(destination.typeName())) { 626 throw new InvalidTypeException("Can't assign object value to a void"); 627 } 628 629 // Validate assignment 630 ReferenceType destType = (ReferenceTypeImpl)destination.type(); 631 ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType(); 632 if (!myType.isAssignableTo(destType)) { 633 JNITypeParser parser = new JNITypeParser(destType.signature()); 634 String destTypeName = parser.typeName(); 635 throw new InvalidTypeException("Can't assign " + 636 type().name() + 637 " to " + destTypeName); 638 } 639 } 640 641 public String toString() { 642 if (isInlineType()) { 643 return "instance of " + referenceType().name(); 644 } else { 645 return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")"; 646 } 647 } 648 649 byte typeValueKey() { 650 return JDWP.Tag.OBJECT; 651 } 652 653 private static boolean isNonVirtual(int options) { 654 return (options & INVOKE_NONVIRTUAL) != 0; 655 } 656 }