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