1 /*
   2  * Copyright (c) 1998, 2017, 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     private boolean isInlineType() {
  80         return referenceType().signature().startsWith("Q");
  81     }
  82 
  83     // Override in subclasses
  84     protected Cache newCache() {
  85         return new Cache();
  86     }
  87 
  88     protected Cache getCache() {
  89         synchronized (vm.state()) {
  90             if (cache == noInitCache) {
  91                 if (vm.state().isSuspended()) {
  92                     // Set cache now, otherwise newly created objects are
  93                     // not cached until resuspend
  94                     enableCache();
  95                 } else {
  96                     disableCache();
  97                 }
  98             }
  99             if (cache == markerCache) {
 100                 cache = newCache();
 101             }
 102             return cache;
 103         }
 104     }
 105 
 106     // Return the ClassTypeImpl upon which to invoke a method.
 107     // By default it is our very own referenceType() but subclasses
 108     // can override.
 109     protected ClassTypeImpl invokableReferenceType(Method method) {
 110         return (ClassTypeImpl)referenceType();
 111     }
 112 
 113     ObjectReferenceImpl(VirtualMachine aVm,long aRef) {
 114         super(aVm);
 115 
 116         ref = aRef;
 117     }
 118 
 119     protected String description() {
 120         return "ObjectReference " + uniqueID();
 121     }
 122 
 123     /*
 124      * VMListener implementation
 125      */
 126     public boolean vmSuspended(VMAction action) {
 127         enableCache();
 128         return true;
 129     }
 130 
 131     public boolean vmNotSuspended(VMAction action) {
 132         // make sure that cache and listener management are synchronized
 133         synchronized (vm.state()) {
 134             if (cache != null && (vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
 135                 vm.printTrace("Clearing temporary cache for " + description());
 136             }
 137             disableCache();
 138             if (addedListener) {
 139                 /*
 140                  * If a listener was added (i.e. this is not a
 141                  * ObjectReference that adds a listener on startup),
 142                  * remove it here.
 143                  */
 144                 addedListener = false;
 145                 return false;  // false says remove
 146             } else {
 147                 return true;
 148             }
 149         }
 150     }
 151 
 152     private boolean isSubtituable(ObjectReferenceImpl other) {
 153         if (referenceType() != other.referenceType()) return false;
 154         List<Field> fields = referenceType().fields();
 155         for (Field f : fields) {
 156             if (f.isStatic()) {
 157                 fields.remove(f);
 158             }
 159         }
 160         Map<Field,Value> thisFields = getValues(fields);
 161         Map<Field,Value> otherFields = other.getValues(fields);
 162         for (Field f : fields) {
 163             if (!thisFields.get(f).equals(otherFields.get(f))) return false;
 164         }
 165         return true;
 166     }
 167 
 168     public boolean equals(Object obj) {
 169         if ((obj != null) && (obj instanceof ObjectReferenceImpl)) {
 170             ObjectReferenceImpl other = (ObjectReferenceImpl) obj;
 171             if (isInlineType()) {
 172                 return isSubtituable(other);
 173 
 174             } else {
 175                 return (ref() == other.ref()) &&
 176                         super.equals(obj);
 177             }
 178         } else {
 179             return false;
 180         }
 181     }
 182 
 183     public int hashCode() {
 184         return(int)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 implict 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         if (isInlineType()) {
 499             throw new UnsupportedOperationException("Inline types cannot have unique IDs");
 500         }
 501         return ref();
 502     }
 503 
 504     JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo()
 505                              throws IncompatibleThreadStateException {
 506         JDWP.ObjectReference.MonitorInfo info = null;
 507         try {
 508             Cache local;
 509 
 510             // getCache() and addlistener() must be synchronized
 511             // so that no events are lost.
 512             synchronized (vm.state()) {
 513                 local = getCache();
 514 
 515                 if (local != null) {
 516                     info = local.monitorInfo;
 517 
 518                     // Check if there will be something to cache
 519                     // and there is not already a listener
 520                     if (info == null && !vm.state().hasListener(this)) {
 521                         /* For other, less numerous objects, this is done
 522                          * in the constructor. Since there can be many
 523                          * ObjectReferences, the VM listener is installed
 524                          * and removed as needed.
 525                          * Listener must be installed before process()
 526                          */
 527                         vm.state().addListener(this);
 528                         addedListener = true;
 529                     }
 530                 }
 531             }
 532             if (info == null) {
 533                 info = JDWP.ObjectReference.MonitorInfo.process(vm, this);
 534                 if (local != null) {
 535                     local.monitorInfo = info;
 536                     if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
 537                         vm.printTrace("ObjectReference " + uniqueID() +
 538                                       " temporarily caching monitor info");
 539                     }
 540                 }
 541             }
 542         } catch (JDWPException exc) {
 543              if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) {
 544                  throw new IncompatibleThreadStateException();
 545              } else {
 546                  throw exc.toJDIException();
 547              }
 548          }
 549         return info;
 550     }
 551 
 552     public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException {
 553         return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters);
 554     }
 555 
 556     public ThreadReference owningThread() throws IncompatibleThreadStateException {
 557         return jdwpMonitorInfo().owner;
 558     }
 559 
 560     public int entryCount() throws IncompatibleThreadStateException {
 561         return jdwpMonitorInfo().entryCount;
 562     }
 563 
 564 
 565     public List<ObjectReference> referringObjects(long maxReferrers) {
 566         if (!vm.canGetInstanceInfo()) {
 567             throw new UnsupportedOperationException(
 568                 "target does not support getting referring objects");
 569         }
 570 
 571         if (maxReferrers < 0) {
 572             throw new IllegalArgumentException("maxReferrers is less than zero: "
 573                                               + maxReferrers);
 574         }
 575 
 576         int intMax = (maxReferrers > Integer.MAX_VALUE)?
 577             Integer.MAX_VALUE: (int)maxReferrers;
 578         // JDWP can't currently handle more than this (in mustang)
 579 
 580         try {
 581             return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects.
 582                                 process(vm, this, intMax).referringObjects);
 583         } catch (JDWPException exc) {
 584             throw exc.toJDIException();
 585         }
 586     }
 587 
 588     long ref() {
 589         return ref;
 590     }
 591 
 592     boolean isClassObject() {
 593         /*
 594          * Don't need to worry about subclasses since java.lang.Class is final.
 595          */
 596         return referenceType().name().equals("java.lang.Class");
 597     }
 598 
 599     ValueImpl prepareForAssignmentTo(ValueContainer destination)
 600                                  throws InvalidTypeException,
 601                                         ClassNotLoadedException {
 602 
 603         validateAssignment(destination);
 604         return this;            // conversion never necessary
 605     }
 606 
 607     void validateAssignment(ValueContainer destination)
 608                             throws InvalidTypeException, ClassNotLoadedException {
 609 
 610         /*
 611          * Do these simpler checks before attempting a query of the destination's
 612          * type which might cause a confusing ClassNotLoadedException if
 613          * the destination is primitive or an array.
 614          */
 615         /*
 616          * TO DO: Centralize JNI signature knowledge
 617          */
 618         if (destination.signature().length() == 1) {
 619             throw new InvalidTypeException("Can't assign object value to primitive");
 620         }
 621         if ((destination.signature().charAt(0) == '[') &&
 622             (type().signature().charAt(0) != '[')) {
 623             throw new InvalidTypeException("Can't assign non-array value to an array");
 624         }
 625         if ("void".equals(destination.typeName())) {
 626             throw new InvalidTypeException("Can't assign object value to a void");
 627         }
 628 
 629         // Validate assignment
 630         ReferenceType destType = (ReferenceTypeImpl)destination.type();
 631         ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();
 632         if (!myType.isAssignableTo(destType)) {
 633             JNITypeParser parser = new JNITypeParser(destType.signature());
 634             String destTypeName = parser.typeName();
 635             throw new InvalidTypeException("Can't assign " +
 636                                            type().name() +
 637                                            " to " + destTypeName);
 638         }
 639     }
 640 
 641     public String toString() {
 642         if (isInlineType()) {
 643             return "instance of " + referenceType().name();
 644         } else {
 645             return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";
 646         }
 647     }
 648 
 649     byte typeValueKey() {
 650         return JDWP.Tag.OBJECT;
 651     }
 652 
 653     private static boolean isNonVirtual(int options) {
 654         return (options & INVOKE_NONVIRTUAL) != 0;
 655     }
 656 }