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 }