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     // 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 != null) && (obj instanceof ObjectReferenceImpl)) {
 150             ObjectReferenceImpl other = (ObjectReferenceImpl)obj;
 151             return (ref() == other.ref()) &&
 152                    super.equals(obj);
 153         } else {
 154             return false;
 155         }
 156     }
 157 
 158     public int hashCode() {
 159         return(int)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 implict 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          * TO DO: Centralize JNI signature knowledge
 589          */
 590         if (destination.signature().length() == 1) {
 591             throw new InvalidTypeException("Can't assign object value to primitive");
 592         }
 593         if ((destination.signature().charAt(0) == '[') &&
 594             (type().signature().charAt(0) != '[')) {
 595             throw new InvalidTypeException("Can't assign non-array value to an array");
 596         }
 597         if ("void".equals(destination.typeName())) {
 598             throw new InvalidTypeException("Can't assign object value to a void");
 599         }
 600 
 601         // Validate assignment
 602         ReferenceType destType = (ReferenceTypeImpl)destination.type();
 603         ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();
 604         if (!myType.isAssignableTo(destType)) {
 605             JNITypeParser parser = new JNITypeParser(destType.signature());
 606             String destTypeName = parser.typeName();
 607             throw new InvalidTypeException("Can't assign " +
 608                                            type().name() +
 609                                            " to " + destTypeName);
 610         }
 611     }
 612 
 613     public String toString() {
 614         return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";
 615     }
 616 
 617     byte typeValueKey() {
 618         return JDWP.Tag.OBJECT;
 619     }
 620 
 621     private static boolean isNonVirtual(int options) {
 622         return (options & INVOKE_NONVIRTUAL) != 0;
 623     }
 624 }