1 /*
   2  * Copyright (c) 1998, 2016, 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 com.sun.jdi.*;
  29 
  30 import java.util.*;
  31 import java.util.ArrayList;
  32 
  33 public class ObjectReferenceImpl extends ValueImpl
  34              implements ObjectReference, VMListener {
  35 
  36     protected long ref;
  37     private ReferenceType type = null;
  38     private int gcDisableCount = 0;
  39     boolean addedListener = false;
  40 
  41     // This is cached only while the VM is suspended
  42     protected static class Cache {
  43         JDWP.ObjectReference.MonitorInfo monitorInfo = null;
  44     }
  45 
  46     private static final Cache noInitCache = new Cache();
  47     private static final Cache markerCache = new Cache();
  48     private Cache cache = noInitCache;
  49 
  50     private void disableCache() {
  51         synchronized (vm.state()) {
  52             cache = null;
  53         }
  54     }
  55 
  56     private void enableCache() {
  57         synchronized (vm.state()) {
  58             cache = markerCache;
  59         }
  60     }
  61 
  62     // Override in subclasses
  63     protected Cache newCache() {
  64         return new Cache();
  65     }
  66 
  67     protected Cache getCache() {
  68         synchronized (vm.state()) {
  69             if (cache == noInitCache) {
  70                 if (vm.state().isSuspended()) {
  71                     // Set cache now, otherwise newly created objects are
  72                     // not cached until resuspend
  73                     enableCache();
  74                 } else {
  75                     disableCache();
  76                 }
  77             }
  78             if (cache == markerCache) {
  79                 cache = newCache();
  80             }
  81             return cache;
  82         }
  83     }
  84 
  85     // Return the ClassTypeImpl upon which to invoke a method.
  86     // By default it is our very own referenceType() but subclasses
  87     // can override.
  88     protected ClassTypeImpl invokableReferenceType(Method method) {
  89         return (ClassTypeImpl)referenceType();
  90     }
  91 
  92     ObjectReferenceImpl(VirtualMachine aVm,long aRef) {
  93         super(aVm);
  94 
  95         ref = aRef;
  96     }
  97 
  98     protected String description() {
  99         return "ObjectReference " + uniqueID();
 100     }
 101 
 102     /*
 103      * VMListener implementation
 104      */
 105     public boolean vmSuspended(VMAction action) {
 106         enableCache();
 107         return true;
 108     }
 109 
 110     public boolean vmNotSuspended(VMAction action) {
 111         // make sure that cache and listener management are synchronized
 112         synchronized (vm.state()) {
 113             if (cache != null && (vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
 114                 vm.printTrace("Clearing temporary cache for " + description());
 115             }
 116             disableCache();
 117             if (addedListener) {
 118                 /*
 119                  * If a listener was added (i.e. this is not a
 120                  * ObjectReference that adds a listener on startup),
 121                  * remove it here.
 122                  */
 123                 addedListener = false;
 124                 return false;  // false says remove
 125             } else {
 126                 return true;
 127             }
 128         }
 129     }
 130 
 131     public boolean equals(Object obj) {
 132         if ((obj != null) && (obj instanceof ObjectReferenceImpl)) {
 133             ObjectReferenceImpl other = (ObjectReferenceImpl)obj;
 134             return (ref() == other.ref()) &&
 135                    super.equals(obj);
 136         } else {
 137             return false;
 138         }
 139     }
 140 
 141     public int hashCode() {
 142         return(int)ref();
 143     }
 144 
 145     public Type type() {
 146         return referenceType();
 147     }
 148 
 149     public ReferenceType referenceType() {
 150         if (type == null) {
 151             try {
 152                 JDWP.ObjectReference.ReferenceType rtinfo =
 153                     JDWP.ObjectReference.ReferenceType.process(vm, this);
 154                 type = vm.referenceType(rtinfo.typeID,
 155                                         rtinfo.refTypeTag);
 156             } catch (JDWPException exc) {
 157                 throw exc.toJDIException();
 158             }
 159         }
 160         return type;
 161     }
 162 
 163     public Value getValue(Field sig) {
 164         List<Field> list = new ArrayList<Field>(1);
 165         list.add(sig);
 166         Map<Field, Value> map = getValues(list);
 167         return map.get(sig);
 168     }
 169 
 170     public Map<Field,Value> getValues(List<? extends Field> theFields) {
 171         validateMirrors(theFields);
 172 
 173         List<Field> staticFields = new ArrayList<Field>(0);
 174         int size = theFields.size();
 175         List<Field> instanceFields = new ArrayList<Field>(size);
 176 
 177         for (int i=0; i<size; i++) {
 178             Field field = (Field)theFields.get(i);
 179 
 180             // Make sure the field is valid
 181             ((ReferenceTypeImpl)referenceType()).validateFieldAccess(field);
 182 
 183             // FIX ME! We need to do some sanity checking
 184             // here; make sure the field belongs to this
 185             // object.
 186             if (field.isStatic())
 187                 staticFields.add(field);
 188             else {
 189                 instanceFields.add(field);
 190             }
 191         }
 192 
 193         Map<Field, Value> map;
 194         if (staticFields.size() > 0) {
 195             map = referenceType().getValues(staticFields);
 196         } else {
 197             map = new HashMap<Field, Value>(size);
 198         }
 199 
 200         size = instanceFields.size();
 201 
 202         JDWP.ObjectReference.GetValues.Field[] queryFields =
 203                          new JDWP.ObjectReference.GetValues.Field[size];
 204         for (int i=0; i<size; i++) {
 205             FieldImpl field = (FieldImpl)instanceFields.get(i);/* thanks OTI */
 206             queryFields[i] = new JDWP.ObjectReference.GetValues.Field(
 207                                          field.ref());
 208         }
 209         ValueImpl[] values;
 210         try {
 211             values = JDWP.ObjectReference.GetValues.
 212                                      process(vm, this, queryFields).values;
 213         } catch (JDWPException exc) {
 214             throw exc.toJDIException();
 215         }
 216 
 217         if (size != values.length) {
 218             throw new InternalException(
 219                          "Wrong number of values returned from target VM");
 220         }
 221         for (int i=0; i<size; i++) {
 222             FieldImpl field = (FieldImpl)instanceFields.get(i);
 223             map.put(field, values[i]);
 224         }
 225 
 226         return map;
 227     }
 228 
 229     public void setValue(Field field, Value value)
 230                    throws InvalidTypeException, ClassNotLoadedException {
 231 
 232         validateMirror(field);
 233         validateMirrorOrNull(value);
 234 
 235         // Make sure the field is valid
 236         ((ReferenceTypeImpl)referenceType()).validateFieldSet(field);
 237 
 238         if (field.isStatic()) {
 239             ReferenceType type = referenceType();
 240             if (type instanceof ClassType) {
 241                 ((ClassType)type).setValue(field, value);
 242                 return;
 243             } else {
 244                 throw new IllegalArgumentException(
 245                                     "Invalid type for static field set");
 246             }
 247         }
 248 
 249         try {
 250             JDWP.ObjectReference.SetValues.FieldValue[] fvals =
 251                       new JDWP.ObjectReference.SetValues.FieldValue[1];
 252             fvals[0] = new JDWP.ObjectReference.SetValues.FieldValue(
 253                            ((FieldImpl)field).ref(),
 254                            // Validate and convert if necessary
 255                            ValueImpl.prepareForAssignment(value,
 256                                                           (FieldImpl)field));
 257             try {
 258                 JDWP.ObjectReference.SetValues.process(vm, this, fvals);
 259             } catch (JDWPException exc) {
 260                 throw exc.toJDIException();
 261             }
 262         } catch (ClassNotLoadedException e) {
 263             /*
 264              * Since we got this exception,
 265              * the field type must be a reference type. The value
 266              * we're trying to set is null, but if the field's
 267              * class has not yet been loaded through the enclosing
 268              * class loader, then setting to null is essentially a
 269              * no-op, and we should allow it without an exception.
 270              */
 271             if (value != null) {
 272                 throw e;
 273             }
 274         }
 275     }
 276 
 277     void validateMethodInvocation(Method method, int options)
 278                                          throws InvalidTypeException,
 279                                          InvocationException {
 280         /*
 281          * Method must be in this object's class, a superclass, or
 282          * implemented interface
 283          */
 284         ReferenceTypeImpl declType = (ReferenceTypeImpl)method.declaringType();
 285 
 286         if (!declType.isAssignableFrom(this)) {
 287             throw new IllegalArgumentException("Invalid method");
 288         }
 289 
 290         if (declType instanceof ClassTypeImpl) {
 291             validateClassMethodInvocation(method, options);
 292         } else if (declType instanceof InterfaceTypeImpl) {
 293             validateIfaceMethodInvocation(method, options);
 294         } else {
 295             throw new InvalidTypeException();
 296         }
 297     }
 298 
 299     void validateClassMethodInvocation(Method method, int options)
 300                                          throws InvalidTypeException,
 301                                          InvocationException {
 302 
 303         ClassTypeImpl clazz = invokableReferenceType(method);
 304 
 305         /*
 306          * Method must be a non-constructor
 307          */
 308         if (method.isConstructor()) {
 309             throw new IllegalArgumentException("Cannot invoke constructor");
 310         }
 311 
 312         /*
 313          * For nonvirtual invokes, method must have a body
 314          */
 315         if (isNonVirtual(options)) {
 316             if (method.isAbstract()) {
 317                 throw new IllegalArgumentException("Abstract method");
 318             }
 319         }
 320 
 321         /*
 322          * Get the class containing the method that will be invoked.
 323          * This class is needed only for proper validation of the
 324          * method argument types.
 325          */
 326         ClassTypeImpl invokedClass;
 327         if (isNonVirtual(options)) {
 328             // No overrides in non-virtual invokes
 329             invokedClass = clazz;
 330         } else {
 331             /*
 332              * For virtual invokes, find any override of the method.
 333              * Since we are looking for a method with a real body, we
 334              * don't need to bother with interfaces/abstract methods.
 335              */
 336             Method invoker = clazz.concreteMethodByName(method.name(),
 337                                                         method.signature());
 338             //  invoker is supposed to be non-null under normal circumstances
 339             invokedClass = (ClassTypeImpl)invoker.declaringType();
 340         }
 341         /* The above code is left over from previous versions.
 342          * We haven't had time to divine the intent.  jjh, 7/31/2003
 343          */
 344     }
 345 
 346     void validateIfaceMethodInvocation(Method method, int options)
 347                                          throws InvalidTypeException,
 348                                          InvocationException {
 349         /*
 350          * For nonvirtual invokes, method must have a body
 351          */
 352         if (isNonVirtual(options)) {
 353             if (method.isAbstract()) {
 354                 throw new IllegalArgumentException("Abstract method");
 355             }
 356         }
 357     }
 358 
 359     PacketStream sendInvokeCommand(final ThreadReferenceImpl thread,
 360                                    final ClassTypeImpl refType,
 361                                    final MethodImpl method,
 362                                    final ValueImpl[] args,
 363                                    final int options) {
 364         CommandSender sender =
 365             new CommandSender() {
 366                 public PacketStream send() {
 367                     return JDWP.ObjectReference.InvokeMethod.enqueueCommand(
 368                                           vm, ObjectReferenceImpl.this,
 369                                           thread, refType,
 370                                           method.ref(), args, options);
 371                 }
 372         };
 373 
 374         PacketStream stream;
 375         if ((options & INVOKE_SINGLE_THREADED) != 0) {
 376             stream = thread.sendResumingCommand(sender);
 377         } else {
 378             stream = vm.sendResumingCommand(sender);
 379         }
 380         return stream;
 381     }
 382 
 383     public Value invokeMethod(ThreadReference threadIntf, Method methodIntf,
 384                               List<? extends Value> origArguments, int options)
 385                               throws InvalidTypeException,
 386                                      IncompatibleThreadStateException,
 387                                      InvocationException,
 388                                      ClassNotLoadedException {
 389 
 390         validateMirror(threadIntf);
 391         validateMirror(methodIntf);
 392         validateMirrorsOrNulls(origArguments);
 393 
 394         MethodImpl method = (MethodImpl)methodIntf;
 395         ThreadReferenceImpl thread = (ThreadReferenceImpl)threadIntf;
 396 
 397         if (method.isStatic()) {
 398             if (referenceType() instanceof InterfaceType) {
 399                 InterfaceType type = (InterfaceType)referenceType();
 400                 return type.invokeMethod(thread, method, origArguments, options);
 401             } else if (referenceType() instanceof ClassType) {
 402                 ClassType type = (ClassType)referenceType();
 403                 return type.invokeMethod(thread, method, origArguments, options);
 404             } else {
 405                 throw new IllegalArgumentException("Invalid type for static method invocation");
 406             }
 407         }
 408 
 409         validateMethodInvocation(method, options);
 410 
 411         List<Value> arguments = method.validateAndPrepareArgumentsForInvoke(
 412                                                   origArguments);
 413 
 414         ValueImpl[] args = arguments.toArray(new ValueImpl[0]);
 415         JDWP.ObjectReference.InvokeMethod ret;
 416         try {
 417             PacketStream stream =
 418                 sendInvokeCommand(thread, invokableReferenceType(method),
 419                                   method, args, options);
 420             ret = JDWP.ObjectReference.InvokeMethod.waitForReply(vm, stream);
 421         } catch (JDWPException exc) {
 422             if (exc.errorCode() == JDWP.Error.INVALID_THREAD) {
 423                 throw new IncompatibleThreadStateException();
 424             } else {
 425                 throw exc.toJDIException();
 426             }
 427         }
 428 
 429         /*
 430          * There is an implict VM-wide suspend at the conclusion
 431          * of a normal (non-single-threaded) method invoke
 432          */
 433         if ((options & INVOKE_SINGLE_THREADED) == 0) {
 434             vm.notifySuspend();
 435         }
 436 
 437         if (ret.exception != null) {
 438             throw new InvocationException(ret.exception);
 439         } else {
 440             return ret.returnValue;
 441         }
 442     }
 443 
 444     /* leave synchronized to keep count accurate */
 445     public synchronized void disableCollection() {
 446         if (gcDisableCount == 0) {
 447             try {
 448                 JDWP.ObjectReference.DisableCollection.process(vm, this);
 449             } catch (JDWPException exc) {
 450                 throw exc.toJDIException();
 451             }
 452         }
 453         gcDisableCount++;
 454     }
 455 
 456     /* leave synchronized to keep count accurate */
 457     public synchronized void enableCollection() {
 458         gcDisableCount--;
 459 
 460         if (gcDisableCount == 0) {
 461             try {
 462                 JDWP.ObjectReference.EnableCollection.process(vm, this);
 463             } catch (JDWPException exc) {
 464                 // If already collected, no harm done, no exception
 465                 if (exc.errorCode() != JDWP.Error.INVALID_OBJECT) {
 466                     throw exc.toJDIException();
 467                 }
 468                 return;
 469             }
 470         }
 471     }
 472 
 473     public boolean isCollected() {
 474         try {
 475             return JDWP.ObjectReference.IsCollected.process(vm, this).
 476                                                               isCollected;
 477         } catch (JDWPException exc) {
 478             throw exc.toJDIException();
 479         }
 480     }
 481 
 482     public long uniqueID() {
 483         return ref();
 484     }
 485 
 486     JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo()
 487                              throws IncompatibleThreadStateException {
 488         JDWP.ObjectReference.MonitorInfo info = null;
 489         try {
 490             Cache local;
 491 
 492             // getCache() and addlistener() must be synchronized
 493             // so that no events are lost.
 494             synchronized (vm.state()) {
 495                 local = getCache();
 496 
 497                 if (local != null) {
 498                     info = local.monitorInfo;
 499 
 500                     // Check if there will be something to cache
 501                     // and there is not already a listener
 502                     if (info == null && !vm.state().hasListener(this)) {
 503                         /* For other, less numerous objects, this is done
 504                          * in the constructor. Since there can be many
 505                          * ObjectReferences, the VM listener is installed
 506                          * and removed as needed.
 507                          * Listener must be installed before process()
 508                          */
 509                         vm.state().addListener(this);
 510                         addedListener = true;
 511                     }
 512                 }
 513             }
 514             if (info == null) {
 515                 info = JDWP.ObjectReference.MonitorInfo.process(vm, this);
 516                 if (local != null) {
 517                     local.monitorInfo = info;
 518                     if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
 519                         vm.printTrace("ObjectReference " + uniqueID() +
 520                                       " temporarily caching monitor info");
 521                     }
 522                 }
 523             }
 524         } catch (JDWPException exc) {
 525              if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) {
 526                  throw new IncompatibleThreadStateException();
 527              } else {
 528                  throw exc.toJDIException();
 529              }
 530          }
 531         return info;
 532     }
 533 
 534     public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException {
 535         return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters);
 536     }
 537 
 538     public ThreadReference owningThread() throws IncompatibleThreadStateException {
 539         return jdwpMonitorInfo().owner;
 540     }
 541 
 542     public int entryCount() throws IncompatibleThreadStateException {
 543         return jdwpMonitorInfo().entryCount;
 544     }
 545 
 546 
 547     public List<ObjectReference> referringObjects(long maxReferrers) {
 548         if (!vm.canGetInstanceInfo()) {
 549             throw new UnsupportedOperationException(
 550                 "target does not support getting referring objects");
 551         }
 552 
 553         if (maxReferrers < 0) {
 554             throw new IllegalArgumentException("maxReferrers is less than zero: "
 555                                               + maxReferrers);
 556         }
 557 
 558         int intMax = (maxReferrers > Integer.MAX_VALUE)?
 559             Integer.MAX_VALUE: (int)maxReferrers;
 560         // JDWP can't currently handle more than this (in mustang)
 561 
 562         try {
 563             return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects.
 564                                 process(vm, this, intMax).referringObjects);
 565         } catch (JDWPException exc) {
 566             throw exc.toJDIException();
 567         }
 568     }
 569 
 570     long ref() {
 571         return ref;
 572     }
 573 
 574     boolean isClassObject() {
 575         /*
 576          * Don't need to worry about subclasses since java.lang.Class is final.
 577          */
 578         return referenceType().name().equals("java.lang.Class");
 579     }
 580 
 581     ValueImpl prepareForAssignmentTo(ValueContainer destination)
 582                                  throws InvalidTypeException,
 583                                         ClassNotLoadedException {
 584 
 585         validateAssignment(destination);
 586         return this;            // conversion never necessary
 587     }
 588 
 589     void validateAssignment(ValueContainer destination)
 590                             throws InvalidTypeException, ClassNotLoadedException {
 591 
 592         /*
 593          * Do these simpler checks before attempting a query of the destination's
 594          * type which might cause a confusing ClassNotLoadedException if
 595          * the destination is primitive or an array.
 596          */
 597         /*
 598          * TO DO: Centralize JNI signature knowledge
 599          */
 600         if (destination.signature().length() == 1) {
 601             throw new InvalidTypeException("Can't assign object value to primitive");
 602         }
 603         if ((destination.signature().charAt(0) == '[') &&
 604             (type().signature().charAt(0) != '[')) {
 605             throw new InvalidTypeException("Can't assign non-array value to an array");
 606         }
 607         if ("void".equals(destination.typeName())) {
 608             throw new InvalidTypeException("Can't assign object value to a void");
 609         }
 610 
 611         // Validate assignment
 612         ReferenceType destType = (ReferenceTypeImpl)destination.type();
 613         ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();
 614         if (!myType.isAssignableTo(destType)) {
 615             JNITypeParser parser = new JNITypeParser(destType.signature());
 616             String destTypeName = parser.typeName();
 617             throw new InvalidTypeException("Can't assign " +
 618                                            type().name() +
 619                                            " to " + destTypeName);
 620         }
 621     }
 622 
 623 
 624     public String toString() {
 625         return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";
 626     }
 627 
 628     byte typeValueKey() {
 629         return JDWP.Tag.OBJECT;
 630     }
 631 
 632     private static boolean isNonVirtual(int options) {
 633         return (options & INVOKE_NONVIRTUAL) != 0;
 634     }
 635 }