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 }