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