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 }