blob: 936b268a56f5b7f1647dd281500479084f481885 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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$
}
}