Path: blob/master/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java
41161 views
/*1* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.tools.jdi;2627import java.util.ArrayList;28import java.util.Arrays;29import java.util.HashMap;30import java.util.List;31import java.util.Map;3233import com.sun.jdi.ClassNotLoadedException;34import com.sun.jdi.ClassType;35import com.sun.jdi.Field;36import com.sun.jdi.IncompatibleThreadStateException;37import com.sun.jdi.InterfaceType;38import com.sun.jdi.InternalException;39import com.sun.jdi.InvalidTypeException;40import com.sun.jdi.InvocationException;41import com.sun.jdi.Method;42import com.sun.jdi.ObjectReference;43import com.sun.jdi.ReferenceType;44import com.sun.jdi.ThreadReference;45import com.sun.jdi.Type;46import com.sun.jdi.Value;47import com.sun.jdi.VirtualMachine;4849public class ObjectReferenceImpl extends ValueImpl50implements ObjectReference, VMListener51{52protected long ref;53private ReferenceType type = null;54private int gcDisableCount = 0;55boolean addedListener = false;5657// This is cached only while the VM is suspended58protected static class Cache {59JDWP.ObjectReference.MonitorInfo monitorInfo = null;60}6162private static final Cache noInitCache = new Cache();63private static final Cache markerCache = new Cache();64private Cache cache = noInitCache;6566private void disableCache() {67synchronized (vm.state()) {68cache = null;69}70}7172private void enableCache() {73synchronized (vm.state()) {74cache = markerCache;75}76}7778// Override in subclasses79protected Cache newCache() {80return new Cache();81}8283protected Cache getCache() {84synchronized (vm.state()) {85if (cache == noInitCache) {86if (vm.state().isSuspended()) {87// Set cache now, otherwise newly created objects are88// not cached until resuspend89enableCache();90} else {91disableCache();92}93}94if (cache == markerCache) {95cache = newCache();96}97return cache;98}99}100101// Return the ClassTypeImpl upon which to invoke a method.102// By default it is our very own referenceType() but subclasses103// can override.104protected ClassTypeImpl invokableReferenceType(Method method) {105return (ClassTypeImpl)referenceType();106}107108ObjectReferenceImpl(VirtualMachine aVm,long aRef) {109super(aVm);110111ref = aRef;112}113114protected String description() {115return "ObjectReference " + uniqueID();116}117118/*119* VMListener implementation120*/121public boolean vmSuspended(VMAction action) {122enableCache();123return true;124}125126public boolean vmNotSuspended(VMAction action) {127// make sure that cache and listener management are synchronized128synchronized (vm.state()) {129if (cache != null && (vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {130vm.printTrace("Clearing temporary cache for " + description());131}132disableCache();133if (addedListener) {134/*135* If a listener was added (i.e. this is not a136* ObjectReference that adds a listener on startup),137* remove it here.138*/139addedListener = false;140return false; // false says remove141} else {142return true;143}144}145}146147public boolean equals(Object obj) {148if ((obj != null) && (obj instanceof ObjectReferenceImpl)) {149ObjectReferenceImpl other = (ObjectReferenceImpl)obj;150return (ref() == other.ref()) &&151super.equals(obj);152} else {153return false;154}155}156157public int hashCode() {158return(int)ref();159}160161public Type type() {162return referenceType();163}164165public ReferenceType referenceType() {166if (type == null) {167try {168JDWP.ObjectReference.ReferenceType rtinfo =169JDWP.ObjectReference.ReferenceType.process(vm, this);170type = vm.referenceType(rtinfo.typeID,171rtinfo.refTypeTag);172} catch (JDWPException exc) {173throw exc.toJDIException();174}175}176return type;177}178179public Value getValue(Field sig) {180List<Field> list = new ArrayList<>(1);181list.add(sig);182Map<Field, Value> map = getValues(list);183return map.get(sig);184}185186public Map<Field,Value> getValues(List<? extends Field> theFields) {187validateMirrors(theFields);188189List<Field> staticFields = new ArrayList<>(0);190int size = theFields.size();191List<Field> instanceFields = new ArrayList<>(size);192193for (int i = 0; i < size; i++) {194Field field = theFields.get(i);195196// Make sure the field is valid197((ReferenceTypeImpl)referenceType()).validateFieldAccess(field);198199// FIX ME! We need to do some sanity checking200// here; make sure the field belongs to this201// object.202if (field.isStatic())203staticFields.add(field);204else {205instanceFields.add(field);206}207}208209Map<Field, Value> map;210if (staticFields.size() > 0) {211map = referenceType().getValues(staticFields);212} else {213map = new HashMap<Field, Value>(size);214}215216size = instanceFields.size();217218JDWP.ObjectReference.GetValues.Field[] queryFields =219new JDWP.ObjectReference.GetValues.Field[size];220for (int i=0; i<size; i++) {221FieldImpl field = (FieldImpl)instanceFields.get(i);/* thanks OTI */222queryFields[i] = new JDWP.ObjectReference.GetValues.Field(223field.ref());224}225ValueImpl[] values;226try {227values = JDWP.ObjectReference.GetValues.228process(vm, this, queryFields).values;229} catch (JDWPException exc) {230throw exc.toJDIException();231}232233if (size != values.length) {234throw new InternalException(235"Wrong number of values returned from target VM");236}237for (int i=0; i<size; i++) {238FieldImpl field = (FieldImpl)instanceFields.get(i);239map.put(field, values[i]);240}241242return map;243}244245public void setValue(Field field, Value value)246throws InvalidTypeException, ClassNotLoadedException {247248validateMirror(field);249validateMirrorOrNull(value);250251// Make sure the field is valid252((ReferenceTypeImpl)referenceType()).validateFieldSet(field);253254if (field.isStatic()) {255ReferenceType type = referenceType();256if (type instanceof ClassType) {257((ClassType)type).setValue(field, value);258return;259} else {260throw new IllegalArgumentException(261"Invalid type for static field set");262}263}264265try {266JDWP.ObjectReference.SetValues.FieldValue[] fvals =267new JDWP.ObjectReference.SetValues.FieldValue[1];268fvals[0] = new JDWP.ObjectReference.SetValues.FieldValue(269((FieldImpl)field).ref(),270// Validate and convert if necessary271ValueImpl.prepareForAssignment(value,272(FieldImpl)field));273try {274JDWP.ObjectReference.SetValues.process(vm, this, fvals);275} catch (JDWPException exc) {276throw exc.toJDIException();277}278} catch (ClassNotLoadedException e) {279/*280* Since we got this exception,281* the field type must be a reference type. The value282* we're trying to set is null, but if the field's283* class has not yet been loaded through the enclosing284* class loader, then setting to null is essentially a285* no-op, and we should allow it without an exception.286*/287if (value != null) {288throw e;289}290}291}292293void validateMethodInvocation(Method method, int options)294throws InvalidTypeException,295InvocationException {296/*297* Method must be in this object's class, a superclass, or298* implemented interface299*/300ReferenceTypeImpl declType = (ReferenceTypeImpl)method.declaringType();301302if (!declType.isAssignableFrom(this)) {303throw new IllegalArgumentException("Invalid method");304}305306if (declType instanceof ClassTypeImpl) {307validateClassMethodInvocation(method, options);308} else if (declType instanceof InterfaceTypeImpl) {309validateIfaceMethodInvocation(method, options);310} else {311throw new InvalidTypeException();312}313}314315void validateClassMethodInvocation(Method method, int options)316throws InvalidTypeException,317InvocationException {318/*319* Method must be a non-constructor320*/321if (method.isConstructor()) {322throw new IllegalArgumentException("Cannot invoke constructor");323}324325/*326* For nonvirtual invokes, method must have a body327*/328if (isNonVirtual(options)) {329if (method.isAbstract()) {330throw new IllegalArgumentException("Abstract method");331}332}333}334335void validateIfaceMethodInvocation(Method method, int options)336throws InvalidTypeException,337InvocationException {338/*339* For nonvirtual invokes, method must have a body340*/341if (isNonVirtual(options)) {342if (method.isAbstract()) {343throw new IllegalArgumentException("Abstract method");344}345}346}347348PacketStream sendInvokeCommand(final ThreadReferenceImpl thread,349final ClassTypeImpl refType,350final MethodImpl method,351final ValueImpl[] args,352final int options) {353CommandSender sender =354new CommandSender() {355public PacketStream send() {356return JDWP.ObjectReference.InvokeMethod.enqueueCommand(357vm, ObjectReferenceImpl.this,358thread, refType,359method.ref(), args, options);360}361};362363PacketStream stream;364if ((options & INVOKE_SINGLE_THREADED) != 0) {365stream = thread.sendResumingCommand(sender);366} else {367stream = vm.sendResumingCommand(sender);368}369return stream;370}371372public Value invokeMethod(ThreadReference threadIntf, Method methodIntf,373List<? extends Value> origArguments, int options)374throws InvalidTypeException,375IncompatibleThreadStateException,376InvocationException,377ClassNotLoadedException {378379validateMirror(threadIntf);380validateMirror(methodIntf);381validateMirrorsOrNulls(origArguments);382383MethodImpl method = (MethodImpl)methodIntf;384ThreadReferenceImpl thread = (ThreadReferenceImpl)threadIntf;385386if (method.isStatic()) {387if (referenceType() instanceof InterfaceType) {388InterfaceType type = (InterfaceType)referenceType();389return type.invokeMethod(thread, method, origArguments, options);390} else if (referenceType() instanceof ClassType) {391ClassType type = (ClassType)referenceType();392return type.invokeMethod(thread, method, origArguments, options);393} else {394throw new IllegalArgumentException("Invalid type for static method invocation");395}396}397398validateMethodInvocation(method, options);399400List<Value> arguments = method.validateAndPrepareArgumentsForInvoke(401origArguments);402403ValueImpl[] args = arguments.toArray(new ValueImpl[0]);404JDWP.ObjectReference.InvokeMethod ret;405try {406PacketStream stream =407sendInvokeCommand(thread, invokableReferenceType(method),408method, args, options);409ret = JDWP.ObjectReference.InvokeMethod.waitForReply(vm, stream);410} catch (JDWPException exc) {411if (exc.errorCode() == JDWP.Error.INVALID_THREAD) {412throw new IncompatibleThreadStateException();413} else {414throw exc.toJDIException();415}416}417418/*419* There is an implict VM-wide suspend at the conclusion420* of a normal (non-single-threaded) method invoke421*/422if ((options & INVOKE_SINGLE_THREADED) == 0) {423vm.notifySuspend();424}425426if (ret.exception != null) {427throw new InvocationException(ret.exception);428} else {429return ret.returnValue;430}431}432433/* leave synchronized to keep count accurate */434public synchronized void disableCollection() {435if (gcDisableCount == 0) {436try {437JDWP.ObjectReference.DisableCollection.process(vm, this);438} catch (JDWPException exc) {439throw exc.toJDIException();440}441}442gcDisableCount++;443}444445/* leave synchronized to keep count accurate */446public synchronized void enableCollection() {447gcDisableCount--;448449if (gcDisableCount == 0) {450try {451JDWP.ObjectReference.EnableCollection.process(vm, this);452} catch (JDWPException exc) {453// If already collected, no harm done, no exception454if (exc.errorCode() != JDWP.Error.INVALID_OBJECT) {455throw exc.toJDIException();456}457return;458}459}460}461462public boolean isCollected() {463try {464return JDWP.ObjectReference.IsCollected.process(vm, this).465isCollected;466} catch (JDWPException exc) {467throw exc.toJDIException();468}469}470471public long uniqueID() {472return ref();473}474475JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo()476throws IncompatibleThreadStateException {477JDWP.ObjectReference.MonitorInfo info = null;478try {479Cache local;480481// getCache() and addlistener() must be synchronized482// so that no events are lost.483synchronized (vm.state()) {484local = getCache();485486if (local != null) {487info = local.monitorInfo;488489// Check if there will be something to cache490// and there is not already a listener491if (info == null && !vm.state().hasListener(this)) {492/* For other, less numerous objects, this is done493* in the constructor. Since there can be many494* ObjectReferences, the VM listener is installed495* and removed as needed.496* Listener must be installed before process()497*/498vm.state().addListener(this);499addedListener = true;500}501}502}503if (info == null) {504info = JDWP.ObjectReference.MonitorInfo.process(vm, this);505if (local != null) {506local.monitorInfo = info;507if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {508vm.printTrace("ObjectReference " + uniqueID() +509" temporarily caching monitor info");510}511}512}513} catch (JDWPException exc) {514if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) {515throw new IncompatibleThreadStateException();516} else {517throw exc.toJDIException();518}519}520return info;521}522523public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException {524return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters);525}526527public ThreadReference owningThread() throws IncompatibleThreadStateException {528return jdwpMonitorInfo().owner;529}530531public int entryCount() throws IncompatibleThreadStateException {532return jdwpMonitorInfo().entryCount;533}534535536public List<ObjectReference> referringObjects(long maxReferrers) {537if (!vm.canGetInstanceInfo()) {538throw new UnsupportedOperationException(539"target does not support getting referring objects");540}541542if (maxReferrers < 0) {543throw new IllegalArgumentException("maxReferrers is less than zero: "544+ maxReferrers);545}546547int intMax = (maxReferrers > Integer.MAX_VALUE)?548Integer.MAX_VALUE: (int)maxReferrers;549// JDWP can't currently handle more than this (in mustang)550551try {552return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects.553process(vm, this, intMax).referringObjects);554} catch (JDWPException exc) {555throw exc.toJDIException();556}557}558559long ref() {560return ref;561}562563boolean isClassObject() {564/*565* Don't need to worry about subclasses since java.lang.Class is final.566*/567return referenceType().name().equals("java.lang.Class");568}569570ValueImpl prepareForAssignmentTo(ValueContainer destination)571throws InvalidTypeException,572ClassNotLoadedException {573574validateAssignment(destination);575return this; // conversion never necessary576}577578void validateAssignment(ValueContainer destination)579throws InvalidTypeException, ClassNotLoadedException {580581/*582* Do these simpler checks before attempting a query of the destination's583* type which might cause a confusing ClassNotLoadedException if584* the destination is primitive or an array.585*/586587JNITypeParser destSig = new JNITypeParser(destination.signature());588if (destSig.isPrimitive()) {589throw new InvalidTypeException("Can't assign object value to primitive");590}591if (destSig.isArray()) {592JNITypeParser sourceSig = new JNITypeParser(type().signature());593if (!sourceSig.isArray()) {594throw new InvalidTypeException("Can't assign non-array value to an array");595}596}597if (destSig.isVoid()) {598throw new InvalidTypeException("Can't assign object value to a void");599}600601// Validate assignment602ReferenceType destType = (ReferenceTypeImpl)destination.type();603ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();604if (!myType.isAssignableTo(destType)) {605JNITypeParser parser = new JNITypeParser(destType.signature());606String destTypeName = parser.typeName();607throw new InvalidTypeException("Can't assign " +608type().name() +609" to " + destTypeName);610}611}612613public String toString() {614return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";615}616617byte typeValueKey() {618return JDWP.Tag.OBJECT;619}620621private static boolean isNonVirtual(int options) {622return (options & INVOKE_NONVIRTUAL) != 0;623}624}625626627