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 }
--- EOF ---