/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdi.internal; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket; import org.eclipse.jdi.internal.jdwp.JdwpID; import org.eclipse.jdi.internal.jdwp.JdwpObjectID; import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket; import com.sun.jdi.ArrayType; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.Field; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.InternalException; import com.sun.jdi.InvalidTypeException; import com.sun.jdi.InvocationException; import com.sun.jdi.Method; import com.sun.jdi.ObjectCollectedException; import com.sun.jdi.ObjectReference; import com.sun.jdi.ReferenceType; import com.sun.jdi.ThreadReference; import com.sun.jdi.Type; import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.Value; /** * this class implements the corresponding interfaces declared by the JDI * specification. See the com.sun.jdi package for more information. * */ public class ObjectReferenceImpl extends ValueImpl implements ObjectReference { /** JDWP Tag. */ public static final byte tag = JdwpID.OBJECT_TAG; /** ObjectID of object that corresponds to this reference. */ private JdwpObjectID fObjectID; /** * Cached reference type. This value is safe for caching because the type of * an object never changes. */ private ReferenceType fReferenceType; /** * Creates new ObjectReferenceImpl. */ public ObjectReferenceImpl(VirtualMachineImpl vmImpl, JdwpObjectID objectID) { this("ObjectReference", vmImpl, objectID); //$NON-NLS-1$ } /** * Creates new ObjectReferenceImpl. */ public ObjectReferenceImpl(String description, VirtualMachineImpl vmImpl, JdwpObjectID objectID) { super(description, vmImpl); fObjectID = objectID; } /** * @returns tag. */ @Override public byte getTag() { return tag; } /** * @return Returns Jdwp Object ID. */ public JdwpObjectID getObjectID() { return fObjectID; } /** * Prevents garbage collection for this object. */ @Override public void disableCollection() { initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_DISABLE_COLLECTION, this); defaultReplyErrorHandler(replyPacket.errorCode()); } finally { handledJdwpRequest(); } } /** * Permits garbage collection for this object. */ @Override public void enableCollection() { initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_ENABLE_COLLECTION, this); defaultReplyErrorHandler(replyPacket.errorCode()); } finally { handledJdwpRequest(); } } /** * Inner class used to return monitor info. */ private class MonitorInfo { ThreadReferenceImpl owner; int entryCount; ArrayList<ThreadReference> waiters; } /** * @return Returns monitor info. */ private MonitorInfo monitorInfo() throws IncompatibleThreadStateException { if (!virtualMachine().canGetMonitorInfo()) { throw new UnsupportedOperationException(); } // Note that this information should not be cached. initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_MONITOR_INFO, this); switch (replyPacket.errorCode()) { case JdwpReplyPacket.INVALID_THREAD: throw new IncompatibleThreadStateException(); case JdwpReplyPacket.THREAD_NOT_SUSPENDED: throw new IncompatibleThreadStateException(); } defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); MonitorInfo result = new MonitorInfo(); result.owner = ThreadReferenceImpl.read(this, replyData); result.entryCount = readInt("entry count", replyData); //$NON-NLS-1$ int nrOfWaiters = readInt("nr of waiters", replyData); //$NON-NLS-1$ result.waiters = new ArrayList<>(nrOfWaiters); for (int i = 0; i < nrOfWaiters; i++) result.waiters.add(ThreadReferenceImpl.read(this, replyData)); return result; } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } /** * @return Returns an ThreadReference for the thread, if any, which * currently owns this object's monitor. */ @Override public ThreadReference owningThread() throws IncompatibleThreadStateException { return monitorInfo().owner; } /** * @return Returns the number times this object's monitor has been entered * by the current owning thread. */ @Override public int entryCount() throws IncompatibleThreadStateException { return monitorInfo().entryCount; } /** * @return Returns a List containing a ThreadReference for each thread * currently waiting for this object's monitor. */ @Override public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException { return monitorInfo().waiters; } /** * @return Returns the value of a given instance or static field in this * object. */ @Override public Value getValue(Field field) { ArrayList<Field> list = new ArrayList<>(1); list.add(field); return getValues(list).get(field); } /** * @return Returns objects that directly reference this object. Only objects * that are reachable for the purposes of garbage collection are * returned. Note that an object can also be referenced in other * ways, such as from a local variable in a stack frame, or from a * JNI global reference. Such non-object referrers are not returned * by this method. * * @since 3.3 */ @Override public List<ObjectReference> referringObjects(long maxReferrers) throws UnsupportedOperationException, IllegalArgumentException { try { int max = (int) maxReferrers; if (maxReferrers >= Integer.MAX_VALUE) { max = Integer.MAX_VALUE; } ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); this.getObjectID().write(outData); writeInt(max, "max referrers", outData); //$NON-NLS-1$ JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_REFERRING_OBJECTS, outBytes); switch (replyPacket.errorCode()) { case JdwpReplyPacket.NOT_IMPLEMENTED: throw new UnsupportedOperationException( JDIMessages.ReferenceTypeImpl_27); case JdwpReplyPacket.ILLEGAL_ARGUMENT: throw new IllegalArgumentException( JDIMessages.ReferenceTypeImpl_26); case JdwpReplyPacket.INVALID_OBJECT: throw new ObjectCollectedException( JDIMessages.ObjectReferenceImpl_object_not_known); case JdwpReplyPacket.VM_DEAD: throw new VMDisconnectedException(JDIMessages.vm_dead); } defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); int elements = readInt("elements", replyData); //$NON-NLS-1$ if (max > 0 && elements > max) { elements = max; } ArrayList<ObjectReference> list = new ArrayList<>(); for (int i = 0; i < elements; i++) { list.add((ObjectReference)ValueImpl.readWithTag(this, replyData)); } return list; } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } /** * @return Returns the value of multiple instance and/or static fields in * this object. */ @Override public Map<Field, Value> getValues(List<? extends Field> allFields) { // if the field list is empty, nothing to do. if (allFields.isEmpty()) { return new HashMap<>(); } // Note that this information should not be cached. initJdwpRequest(); try { ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); /* * Distinguish static fields from non-static fields: For static * fields ReferenceTypeImpl.getValues() must be used. */ List<Field> staticFields = new ArrayList<>(); List<FieldImpl> nonStaticFields = new ArrayList<>(); // Separate static and non-static fields. int allFieldsSize = allFields.size(); for (int i = 0; i < allFieldsSize; i++) { FieldImpl field = (FieldImpl) allFields.get(i); checkVM(field); if (field.isStatic()) staticFields.add(field); else nonStaticFields.add(field); } // First get values for the static fields. Map<Field, Value> resultMap; if (staticFields.isEmpty()) { resultMap = new HashMap<>(); } else { resultMap = referenceType().getValues(staticFields); } // if no non-static fields are requested, return directly the // result. if (nonStaticFields.isEmpty()) { return resultMap; } // Then get the values for the non-static fields. int nonStaticFieldsSize = nonStaticFields.size(); write(this, outData); writeInt(nonStaticFieldsSize, "size", outData); //$NON-NLS-1$ for (int i = 0; i < nonStaticFieldsSize; i++) { FieldImpl field = nonStaticFields.get(i); field.write(this, outData); } JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_GET_VALUES, outBytes); defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); int nrOfElements = readInt("elements", replyData); //$NON-NLS-1$ if (nrOfElements != nonStaticFieldsSize) throw new InternalError( JDIMessages.ObjectReferenceImpl_Retrieved_a_different_number_of_values_from_the_VM_than_requested_1); for (int i = 0; i < nrOfElements; i++) { resultMap.put(nonStaticFields.get(i), ValueImpl.readWithTag(this, replyData)); } return resultMap; } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } /** * @return Returns the hash code value. */ @Override public int hashCode() { return fObjectID.hashCode(); } /** * @return Returns true if two mirrors refer to the same entity in the * target VM. * @see java.lang.Object#equals(Object) */ @Override public boolean equals(Object object) { return object != null && object.getClass().equals(this.getClass()) && fObjectID.equals(((ObjectReferenceImpl) object).fObjectID) && virtualMachine().equals( ((MirrorImpl) object).virtualMachine()); } /** * @return Returns Jdwp version of given options. */ private int optionsToJdwpOptions(int options) { int jdwpOptions = 0; if ((options & INVOKE_SINGLE_THREADED) != 0) { jdwpOptions |= MethodImpl.INVOKE_SINGLE_THREADED_JDWP; } if ((options & INVOKE_NONVIRTUAL) != 0) { jdwpOptions |= MethodImpl.INVOKE_NONVIRTUAL_JDWP; } return jdwpOptions; } /** * Invokes the specified static Method in the target VM. * * @return Returns a Value mirror of the invoked method's return value. */ @Override public Value invokeMethod(ThreadReference thread, Method method, List<? extends Value> arguments, int options) throws InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException { checkVM(thread); checkVM(method); ThreadReferenceImpl threadImpl = (ThreadReferenceImpl) thread; MethodImpl methodImpl = (MethodImpl) method; // Perform some checks for IllegalArgumentException. if (!isAValidMethod(method)) throw new IllegalArgumentException( JDIMessages.ObjectReferenceImpl_Class_does_not_contain_given_method_2); if (method.argumentTypeNames().size() != arguments.size()) throw new IllegalArgumentException( JDIMessages.ObjectReferenceImpl_Number_of_arguments_doesn__t_match_3); if (method.isConstructor() || method.isStaticInitializer()) throw new IllegalArgumentException( JDIMessages.ObjectReferenceImpl_Method_is_constructor_or_intitializer_4); if ((options & INVOKE_NONVIRTUAL) != 0 && method.isAbstract()) throw new IllegalArgumentException( JDIMessages.ObjectReferenceImpl_Method_is_abstract_and_can_therefore_not_be_invoked_nonvirtual_5); // check the type and the vm of the argument, convert the value if // needed. List<Value> checkedArguments = ValueImpl.checkValues(arguments, method.argumentTypes(), virtualMachineImpl()); initJdwpRequest(); try { ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); write(this, outData); threadImpl.write(this, outData); ((ReferenceTypeImpl) referenceType()).write(this, outData); methodImpl.write(this, outData); writeInt(checkedArguments.size(), "size", outData); //$NON-NLS-1$ Iterator<Value> iter = checkedArguments.iterator(); while (iter.hasNext()) { ValueImpl elt = (ValueImpl) iter.next(); if (elt != null) { elt.writeWithTag(this, outData); } else { ValueImpl.writeNullWithTag(this, outData); } } writeInt(optionsToJdwpOptions(options), "options", MethodImpl.getInvokeOptions(), outData); //$NON-NLS-1$ JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_INVOKE_METHOD, outBytes); switch (replyPacket.errorCode()) { case JdwpReplyPacket.TYPE_MISMATCH: throw new InvalidTypeException(); case JdwpReplyPacket.INVALID_CLASS: throw new ClassNotLoadedException( JDIMessages.ObjectReferenceImpl_One_of_the_arguments_of_ObjectReference_invokeMethod___6); case JdwpReplyPacket.INVALID_THREAD: throw new IncompatibleThreadStateException(); case JdwpReplyPacket.THREAD_NOT_SUSPENDED: throw new IncompatibleThreadStateException(); case JdwpReplyPacket.INVALID_TYPESTATE: throw new IncompatibleThreadStateException(); } defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); ValueImpl value = ValueImpl.readWithTag(this, replyData); ObjectReferenceImpl exception = ObjectReferenceImpl .readObjectRefWithTag(this, replyData); if (exception != null) throw new InvocationException(exception); return value; } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } private boolean isAValidMethod(Method method) { ReferenceType refType = referenceType(); if (refType instanceof ArrayType) { // if the object is an array, check if the method is declared in // java.lang.Object return "java.lang.Object".equals(method.declaringType().name()); //$NON-NLS-1$ } return refType.allMethods().contains(method); } /** * @return Returns if this object has been garbage collected in the target * VM. */ @Override public boolean isCollected() { // Note that this information should not be cached. initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_IS_COLLECTED, this); switch (replyPacket.errorCode()) { case JdwpReplyPacket.INVALID_OBJECT: return true; case JdwpReplyPacket.NOT_IMPLEMENTED: // Workaround for problem in J2ME WTK (wireless toolkit) // @see Bug 12966 try { referenceType(); } catch (ObjectCollectedException e) { return true; } return false; default: defaultReplyErrorHandler(replyPacket.errorCode()); break; } DataInputStream replyData = replyPacket.dataInStream(); boolean result = readBoolean("is collected", replyData); //$NON-NLS-1$ return result; } catch (IOException e) { defaultIOExceptionHandler(e); return false; } finally { handledJdwpRequest(); } } /** * @return Returns the ReferenceType that mirrors the type of this object. */ @Override public ReferenceType referenceType() { if (fReferenceType != null) { return fReferenceType; } initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_REFERENCE_TYPE, this); defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); fReferenceType = ReferenceTypeImpl.readWithTypeTag(this, replyData); return fReferenceType; } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } /** * @return Returns the Type that mirrors the type of this object. */ @Override public Type type() { return referenceType(); } /** * Sets the value of a given instance or static field in this object. */ @Override public void setValue(Field field, Value value) throws InvalidTypeException, ClassNotLoadedException { // Note that this information should not be cached. initJdwpRequest(); try { ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); write(this, outData); writeInt(1, "size", outData); // We only set one field //$NON-NLS-1$ checkVM(field); ((FieldImpl) field).write(this, outData); // check the type and the vm of the value. Convert the value if // needed ValueImpl checkedValue = ValueImpl.checkValue(value, field.type(), virtualMachineImpl()); if (checkedValue != null) { checkedValue.write(this, outData); } else { ValueImpl.writeNull(this, outData); } JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.OR_SET_VALUES, outBytes); switch (replyPacket.errorCode()) { case JdwpReplyPacket.TYPE_MISMATCH: throw new InvalidTypeException(); case JdwpReplyPacket.INVALID_CLASS: throw new ClassNotLoadedException(referenceType().name()); } defaultReplyErrorHandler(replyPacket.errorCode()); } catch (IOException e) { defaultIOExceptionHandler(e); } finally { handledJdwpRequest(); } } /** * @return Returns a unique identifier for this ObjectReference. */ @Override public long uniqueID() { return fObjectID.value(); } /** * @return Returns string with value of ID. */ public String idString() { return "(id=" + fObjectID + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } /** * @return Returns description of Mirror object. */ @Override public String toString() { try { return type().toString() + " " + idString(); //$NON-NLS-1$ } catch (ObjectCollectedException e) { return JDIMessages.ObjectReferenceImpl__Garbage_Collected__ObjectReference__8 + idString(); } catch (Exception e) { return fDescription; } } /** * @return Reads JDWP representation and returns new instance. */ public static ObjectReferenceImpl readObjectRefWithoutTag( MirrorImpl target, DataInputStream in) throws IOException { VirtualMachineImpl vmImpl = target.virtualMachineImpl(); JdwpObjectID ID = new JdwpObjectID(vmImpl); ID.read(in); if (target.fVerboseWriter != null) target.fVerboseWriter.println("objectReference", ID.value()); //$NON-NLS-1$ if (ID.isNull()) return null; ObjectReferenceImpl mirror = new ObjectReferenceImpl(vmImpl, ID); return mirror; } /** * @return Reads JDWP representation and returns new instance. */ public static ObjectReferenceImpl readObjectRefWithTag(MirrorImpl target, DataInputStream in) throws IOException { byte objectTag = target.readByte("object tag", JdwpID.tagMap(), in); //$NON-NLS-1$ switch (objectTag) { case 0: return null; case ObjectReferenceImpl.tag: return ObjectReferenceImpl.readObjectRefWithoutTag(target, in); case ArrayReferenceImpl.tag: return ArrayReferenceImpl.read(target, in); case ClassLoaderReferenceImpl.tag: return ClassLoaderReferenceImpl.read(target, in); case ClassObjectReferenceImpl.tag: return ClassObjectReferenceImpl.read(target, in); case StringReferenceImpl.tag: return StringReferenceImpl.read(target, in); case ThreadGroupReferenceImpl.tag: return ThreadGroupReferenceImpl.read(target, in); case ThreadReferenceImpl.tag: return ThreadReferenceImpl.read(target, in); } throw new InternalException( JDIMessages.ObjectReferenceImpl_Invalid_ObjectID_tag_encountered___9 + objectTag); } /** * Writes JDWP representation without tag. */ @Override public void write(MirrorImpl target, DataOutputStream out) throws IOException { fObjectID.write(out); if (target.fVerboseWriter != null) target.fVerboseWriter.println("objectReference", fObjectID.value()); //$NON-NLS-1$ } }