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 boolean isValueObject() {
149 if (referenceType() instanceof ClassTypeImpl classType) {
150 return classType.isValueClass();
151 }
152 return false;
153 }
154
155 public boolean equals(Object obj) {
156 if (obj instanceof ObjectReferenceImpl other) {
157 if (!super.equals(obj)) { // checks if the references belong to the same VM
158 return false;
159 }
160 if (ref() == other.ref()) {
161 return true;
162 }
163 // We can get equal value objects with different IDs.
164 if (isValueObject()) {
165 try {
166 return JDWP.ObjectReference.IsSameObject.process(vm, this, other).isSameObject;
167 } catch (JDWPException exc) {
168 throw exc.toJDIException();
169 }
170 }
171 }
172 return false;
173 }
174
175 @Override
176 public int hashCode() {
177 if (isValueObject()) {
178 try {
179 return JDWP.ObjectReference.ObjectHashCode.process(vm, this).hashCode;
180 } catch (JDWPException exc) {
181 throw exc.toJDIException();
182 }
183 }
184 return Long.hashCode(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 implicit 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 return ref();
499 }
500
501 JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo()
502 throws IncompatibleThreadStateException {
503 JDWP.ObjectReference.MonitorInfo info = null;
504 try {
505 Cache local;
506
507 // getCache() and addlistener() must be synchronized
508 // so that no events are lost.
509 synchronized (vm.state()) {
510 local = getCache();
511
512 if (local != null) {
513 info = local.monitorInfo;
514
515 // Check if there will be something to cache
516 // and there is not already a listener
517 if (info == null && !vm.state().hasListener(this)) {
518 /* For other, less numerous objects, this is done
519 * in the constructor. Since there can be many
520 * ObjectReferences, the VM listener is installed
521 * and removed as needed.
522 * Listener must be installed before process()
523 */
524 vm.state().addListener(this);
525 addedListener = true;
526 }
527 }
528 }
529 if (info == null) {
530 info = JDWP.ObjectReference.MonitorInfo.process(vm, this);
531 if (local != null) {
532 local.monitorInfo = info;
533 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
534 vm.printTrace("ObjectReference " + uniqueID() +
535 " temporarily caching monitor info");
536 }
537 }
538 }
539 } catch (JDWPException exc) {
540 if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) {
541 throw new IncompatibleThreadStateException();
542 } else {
543 throw exc.toJDIException();
544 }
545 }
546 return info;
547 }
548
549 public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException {
550 return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters);
551 }
552
553 public ThreadReference owningThread() throws IncompatibleThreadStateException {
554 return jdwpMonitorInfo().owner;
555 }
556
557 public int entryCount() throws IncompatibleThreadStateException {
558 return jdwpMonitorInfo().entryCount;
559 }
560
561
562 public List<ObjectReference> referringObjects(long maxReferrers) {
563 if (!vm.canGetInstanceInfo()) {
564 throw new UnsupportedOperationException(
565 "target does not support getting referring objects");
566 }
567
568 if (maxReferrers < 0) {
569 throw new IllegalArgumentException("maxReferrers is less than zero: "
570 + maxReferrers);
571 }
572
573 int intMax = (maxReferrers > Integer.MAX_VALUE)?
574 Integer.MAX_VALUE: (int)maxReferrers;
575 // JDWP can't currently handle more than this (in mustang)
576
577 try {
578 return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects.
579 process(vm, this, intMax).referringObjects);
580 } catch (JDWPException exc) {
581 throw exc.toJDIException();
582 }
583 }
584
585 long ref() {
586 return ref;
587 }
588
589 boolean isClassObject() {
590 /*
591 * Don't need to worry about subclasses since java.lang.Class is final.
592 */
593 return referenceType().name().equals("java.lang.Class");
594 }
595
596 ValueImpl prepareForAssignmentTo(ValueContainer destination)
597 throws InvalidTypeException,
598 ClassNotLoadedException {
599
600 validateAssignment(destination);
601 return this; // conversion never necessary
602 }
603
604 void validateAssignment(ValueContainer destination)
605 throws InvalidTypeException, ClassNotLoadedException {
606
607 /*
608 * Do these simpler checks before attempting a query of the destination's
609 * type which might cause a confusing ClassNotLoadedException if
610 * the destination is primitive or an array.
611 */
612
613 JNITypeParser destSig = new JNITypeParser(destination.signature());
614 if (destSig.isPrimitive()) {
615 throw new InvalidTypeException("Can't assign object value to primitive");
616 }
617 if (destSig.isArray()) {
618 JNITypeParser sourceSig = new JNITypeParser(type().signature());
619 if (!sourceSig.isArray()) {
620 throw new InvalidTypeException("Can't assign non-array value to an array");
621 }
622 }
623 if (destSig.isVoid()) {
624 throw new InvalidTypeException("Can't assign object value to a void");
625 }
626
627 // Validate assignment
628 ReferenceType destType = (ReferenceTypeImpl)destination.type();
629 ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();
630 if (!myType.isAssignableTo(destType)) {
631 JNITypeParser parser = new JNITypeParser(destType.signature());
632 String destTypeName = parser.typeName();
633 throw new InvalidTypeException("Can't assign " +
634 type().name() +
635 " to " + destTypeName);
636 }
637 }
638
639 public String toString() {
640 return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";
641 }
642
643 byte typeValueKey() {
644 return JDWP.Tag.OBJECT;
645 }
646
647 private static boolean isNonVirtual(int options) {
648 return (options & INVOKE_NONVIRTUAL) != 0;
649 }
650 }