632 lines
22 KiB
Java
632 lines
22 KiB
Java
/*
|
|
* Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package com.sun.tools.jdi;
|
|
|
|
import com.sun.jdi.*;
|
|
|
|
import java.util.*;
|
|
import java.util.ArrayList;
|
|
|
|
public class ObjectReferenceImpl extends ValueImpl
|
|
implements ObjectReference, VMListener {
|
|
|
|
protected long ref;
|
|
private ReferenceType type = null;
|
|
private int gcDisableCount = 0;
|
|
boolean addedListener = false;
|
|
|
|
// This is cached only while the VM is suspended
|
|
protected static class Cache {
|
|
JDWP.ObjectReference.MonitorInfo monitorInfo = null;
|
|
}
|
|
|
|
private static final Cache noInitCache = new Cache();
|
|
private static final Cache markerCache = new Cache();
|
|
private Cache cache = noInitCache;
|
|
|
|
private void disableCache() {
|
|
synchronized (vm.state()) {
|
|
cache = null;
|
|
}
|
|
}
|
|
|
|
private void enableCache() {
|
|
synchronized (vm.state()) {
|
|
cache = markerCache;
|
|
}
|
|
}
|
|
|
|
// Override in subclasses
|
|
protected Cache newCache() {
|
|
return new Cache();
|
|
}
|
|
|
|
protected Cache getCache() {
|
|
synchronized (vm.state()) {
|
|
if (cache == noInitCache) {
|
|
if (vm.state().isSuspended()) {
|
|
// Set cache now, otherwise newly created objects are
|
|
// not cached until resuspend
|
|
enableCache();
|
|
} else {
|
|
disableCache();
|
|
}
|
|
}
|
|
if (cache == markerCache) {
|
|
cache = newCache();
|
|
}
|
|
return cache;
|
|
}
|
|
}
|
|
|
|
// Return the ClassTypeImpl upon which to invoke a method.
|
|
// By default it is our very own referenceType() but subclasses
|
|
// can override.
|
|
protected ClassTypeImpl invokableReferenceType(Method method) {
|
|
return (ClassTypeImpl)referenceType();
|
|
}
|
|
|
|
ObjectReferenceImpl(VirtualMachine aVm,long aRef) {
|
|
super(aVm);
|
|
|
|
ref = aRef;
|
|
}
|
|
|
|
protected String description() {
|
|
return "ObjectReference " + uniqueID();
|
|
}
|
|
|
|
/*
|
|
* VMListener implementation
|
|
*/
|
|
public boolean vmSuspended(VMAction action) {
|
|
enableCache();
|
|
return true;
|
|
}
|
|
|
|
public boolean vmNotSuspended(VMAction action) {
|
|
// make sure that cache and listener management are synchronized
|
|
synchronized (vm.state()) {
|
|
if (cache != null && (vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
|
|
vm.printTrace("Clearing temporary cache for " + description());
|
|
}
|
|
disableCache();
|
|
if (addedListener) {
|
|
/*
|
|
* If a listener was added (i.e. this is not a
|
|
* ObjectReference that adds a listener on startup),
|
|
* remove it here.
|
|
*/
|
|
addedListener = false;
|
|
return false; // false says remove
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean equals(Object obj) {
|
|
if ((obj != null) && (obj instanceof ObjectReferenceImpl)) {
|
|
ObjectReferenceImpl other = (ObjectReferenceImpl)obj;
|
|
return (ref() == other.ref()) &&
|
|
super.equals(obj);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int hashCode() {
|
|
return(int)ref();
|
|
}
|
|
|
|
public Type type() {
|
|
return referenceType();
|
|
}
|
|
|
|
public ReferenceType referenceType() {
|
|
if (type == null) {
|
|
try {
|
|
JDWP.ObjectReference.ReferenceType rtinfo =
|
|
JDWP.ObjectReference.ReferenceType.process(vm, this);
|
|
type = vm.referenceType(rtinfo.typeID,
|
|
rtinfo.refTypeTag);
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
public Value getValue(Field sig) {
|
|
List<Field> list = new ArrayList<Field>(1);
|
|
list.add(sig);
|
|
Map<Field, Value> map = getValues(list);
|
|
return map.get(sig);
|
|
}
|
|
|
|
public Map<Field,Value> getValues(List<? extends Field> theFields) {
|
|
validateMirrors(theFields);
|
|
|
|
List<Field> staticFields = new ArrayList<Field>(0);
|
|
int size = theFields.size();
|
|
List<Field> instanceFields = new ArrayList<Field>(size);
|
|
|
|
for (int i=0; i<size; i++) {
|
|
Field field = (Field)theFields.get(i);
|
|
|
|
// Make sure the field is valid
|
|
((ReferenceTypeImpl)referenceType()).validateFieldAccess(field);
|
|
|
|
// FIX ME! We need to do some sanity checking
|
|
// here; make sure the field belongs to this
|
|
// object.
|
|
if (field.isStatic())
|
|
staticFields.add(field);
|
|
else {
|
|
instanceFields.add(field);
|
|
}
|
|
}
|
|
|
|
Map<Field, Value> map;
|
|
if (staticFields.size() > 0) {
|
|
map = referenceType().getValues(staticFields);
|
|
} else {
|
|
map = new HashMap<Field, Value>(size);
|
|
}
|
|
|
|
size = instanceFields.size();
|
|
|
|
JDWP.ObjectReference.GetValues.Field[] queryFields =
|
|
new JDWP.ObjectReference.GetValues.Field[size];
|
|
for (int i=0; i<size; i++) {
|
|
FieldImpl field = (FieldImpl)instanceFields.get(i);/* thanks OTI */
|
|
queryFields[i] = new JDWP.ObjectReference.GetValues.Field(
|
|
field.ref());
|
|
}
|
|
ValueImpl[] values;
|
|
try {
|
|
values = JDWP.ObjectReference.GetValues.
|
|
process(vm, this, queryFields).values;
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
|
|
if (size != values.length) {
|
|
throw new InternalException(
|
|
"Wrong number of values returned from target VM");
|
|
}
|
|
for (int i=0; i<size; i++) {
|
|
FieldImpl field = (FieldImpl)instanceFields.get(i);
|
|
map.put(field, values[i]);
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
public void setValue(Field field, Value value)
|
|
throws InvalidTypeException, ClassNotLoadedException {
|
|
|
|
validateMirror(field);
|
|
validateMirrorOrNull(value);
|
|
|
|
// Make sure the field is valid
|
|
((ReferenceTypeImpl)referenceType()).validateFieldSet(field);
|
|
|
|
if (field.isStatic()) {
|
|
ReferenceType type = referenceType();
|
|
if (type instanceof ClassType) {
|
|
((ClassType)type).setValue(field, value);
|
|
return;
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Invalid type for static field set");
|
|
}
|
|
}
|
|
|
|
try {
|
|
JDWP.ObjectReference.SetValues.FieldValue[] fvals =
|
|
new JDWP.ObjectReference.SetValues.FieldValue[1];
|
|
fvals[0] = new JDWP.ObjectReference.SetValues.FieldValue(
|
|
((FieldImpl)field).ref(),
|
|
// Validate and convert if necessary
|
|
ValueImpl.prepareForAssignment(value,
|
|
(FieldImpl)field));
|
|
try {
|
|
JDWP.ObjectReference.SetValues.process(vm, this, fvals);
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
} catch (ClassNotLoadedException e) {
|
|
/*
|
|
* Since we got this exception,
|
|
* the field type must be a reference type. The value
|
|
* we're trying to set is null, but if the field's
|
|
* class has not yet been loaded through the enclosing
|
|
* class loader, then setting to null is essentially a
|
|
* no-op, and we should allow it without an exception.
|
|
*/
|
|
if (value != null) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
void validateMethodInvocation(Method method, int options)
|
|
throws InvalidTypeException,
|
|
InvocationException {
|
|
/*
|
|
* Method must be in this object's class, a superclass, or
|
|
* implemented interface
|
|
*/
|
|
ReferenceTypeImpl declType = (ReferenceTypeImpl)method.declaringType();
|
|
if (!declType.isAssignableFrom(this)) {
|
|
throw new IllegalArgumentException("Invalid method");
|
|
}
|
|
|
|
if (declType instanceof ClassTypeImpl) {
|
|
validateClassMethodInvocation(method, options);
|
|
} else if (declType instanceof InterfaceTypeImpl) {
|
|
validateIfaceMethodInvocation(method, options);
|
|
} else {
|
|
throw new InvalidTypeException();
|
|
}
|
|
}
|
|
|
|
void validateClassMethodInvocation(Method method, int options)
|
|
throws InvalidTypeException,
|
|
InvocationException {
|
|
|
|
ClassTypeImpl clazz = invokableReferenceType(method);
|
|
|
|
/*
|
|
* Method must be a non-constructor
|
|
*/
|
|
if (method.isConstructor()) {
|
|
throw new IllegalArgumentException("Cannot invoke constructor");
|
|
}
|
|
|
|
/*
|
|
* For nonvirtual invokes, method must have a body
|
|
*/
|
|
if (isNonVirtual(options)) {
|
|
if (method.isAbstract()) {
|
|
throw new IllegalArgumentException("Abstract method");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the class containing the method that will be invoked.
|
|
* This class is needed only for proper validation of the
|
|
* method argument types.
|
|
*/
|
|
ClassTypeImpl invokedClass;
|
|
if (isNonVirtual(options)) {
|
|
// No overrides in non-virtual invokes
|
|
invokedClass = clazz;
|
|
} else {
|
|
/*
|
|
* For virtual invokes, find any override of the method.
|
|
* Since we are looking for a method with a real body, we
|
|
* don't need to bother with interfaces/abstract methods.
|
|
*/
|
|
Method invoker = clazz.concreteMethodByName(method.name(),
|
|
method.signature());
|
|
// invoker is supposed to be non-null under normal circumstances
|
|
invokedClass = (ClassTypeImpl)invoker.declaringType();
|
|
}
|
|
/* The above code is left over from previous versions.
|
|
* We haven't had time to divine the intent. jjh, 7/31/2003
|
|
*/
|
|
}
|
|
|
|
void validateIfaceMethodInvocation(Method method, int options)
|
|
throws InvalidTypeException,
|
|
InvocationException {
|
|
/*
|
|
* Only default methods allowed for nonvirtual invokes
|
|
*/
|
|
if (isNonVirtual(options) && !method.isDefault()) {
|
|
throw new IllegalArgumentException("Not a default method");
|
|
}
|
|
}
|
|
|
|
PacketStream sendInvokeCommand(final ThreadReferenceImpl thread,
|
|
final ClassTypeImpl refType,
|
|
final MethodImpl method,
|
|
final ValueImpl[] args,
|
|
final int options) {
|
|
CommandSender sender =
|
|
new CommandSender() {
|
|
public PacketStream send() {
|
|
return JDWP.ObjectReference.InvokeMethod.enqueueCommand(
|
|
vm, ObjectReferenceImpl.this,
|
|
thread, refType,
|
|
method.ref(), args, options);
|
|
}
|
|
};
|
|
|
|
PacketStream stream;
|
|
if ((options & INVOKE_SINGLE_THREADED) != 0) {
|
|
stream = thread.sendResumingCommand(sender);
|
|
} else {
|
|
stream = vm.sendResumingCommand(sender);
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
public Value invokeMethod(ThreadReference threadIntf, Method methodIntf,
|
|
List<? extends Value> origArguments, int options)
|
|
throws InvalidTypeException,
|
|
IncompatibleThreadStateException,
|
|
InvocationException,
|
|
ClassNotLoadedException {
|
|
validateMirror(threadIntf);
|
|
validateMirror(methodIntf);
|
|
validateMirrorsOrNulls(origArguments);
|
|
|
|
MethodImpl method = (MethodImpl)methodIntf;
|
|
ThreadReferenceImpl thread = (ThreadReferenceImpl)threadIntf;
|
|
|
|
if (method.isStatic()) {
|
|
if (referenceType() instanceof InterfaceType) {
|
|
InterfaceType type = (InterfaceType)referenceType();
|
|
return type.invokeMethod(thread, method, origArguments, options);
|
|
} else if (referenceType() instanceof ClassType) {
|
|
ClassType type = (ClassType)referenceType();
|
|
return type.invokeMethod(thread, method, origArguments, options);
|
|
} else {
|
|
throw new IllegalArgumentException("Invalid type for static method invocation");
|
|
}
|
|
}
|
|
|
|
validateMethodInvocation(method, options);
|
|
|
|
List<Value> arguments = method.validateAndPrepareArgumentsForInvoke(
|
|
origArguments);
|
|
|
|
ValueImpl[] args = arguments.toArray(new ValueImpl[0]);
|
|
JDWP.ObjectReference.InvokeMethod ret;
|
|
try {
|
|
PacketStream stream =
|
|
sendInvokeCommand(thread, invokableReferenceType(method),
|
|
method, args, options);
|
|
ret = JDWP.ObjectReference.InvokeMethod.waitForReply(vm, stream);
|
|
} catch (JDWPException exc) {
|
|
if (exc.errorCode() == JDWP.Error.INVALID_THREAD) {
|
|
throw new IncompatibleThreadStateException();
|
|
} else {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There is an implict VM-wide suspend at the conclusion
|
|
* of a normal (non-single-threaded) method invoke
|
|
*/
|
|
if ((options & INVOKE_SINGLE_THREADED) == 0) {
|
|
vm.notifySuspend();
|
|
}
|
|
|
|
if (ret.exception != null) {
|
|
throw new InvocationException(ret.exception);
|
|
} else {
|
|
return ret.returnValue;
|
|
}
|
|
}
|
|
|
|
/* leave synchronized to keep count accurate */
|
|
public synchronized void disableCollection() {
|
|
if (gcDisableCount == 0) {
|
|
try {
|
|
JDWP.ObjectReference.DisableCollection.process(vm, this);
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
gcDisableCount++;
|
|
}
|
|
|
|
/* leave synchronized to keep count accurate */
|
|
public synchronized void enableCollection() {
|
|
gcDisableCount--;
|
|
|
|
if (gcDisableCount == 0) {
|
|
try {
|
|
JDWP.ObjectReference.EnableCollection.process(vm, this);
|
|
} catch (JDWPException exc) {
|
|
// If already collected, no harm done, no exception
|
|
if (exc.errorCode() != JDWP.Error.INVALID_OBJECT) {
|
|
throw exc.toJDIException();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isCollected() {
|
|
try {
|
|
return JDWP.ObjectReference.IsCollected.process(vm, this).
|
|
isCollected;
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
|
|
public long uniqueID() {
|
|
return ref();
|
|
}
|
|
|
|
JDWP.ObjectReference.MonitorInfo jdwpMonitorInfo()
|
|
throws IncompatibleThreadStateException {
|
|
JDWP.ObjectReference.MonitorInfo info = null;
|
|
try {
|
|
Cache local;
|
|
|
|
// getCache() and addlistener() must be synchronized
|
|
// so that no events are lost.
|
|
synchronized (vm.state()) {
|
|
local = getCache();
|
|
|
|
if (local != null) {
|
|
info = local.monitorInfo;
|
|
|
|
// Check if there will be something to cache
|
|
// and there is not already a listener
|
|
if (info == null && !vm.state().hasListener(this)) {
|
|
/* For other, less numerous objects, this is done
|
|
* in the constructor. Since there can be many
|
|
* ObjectReferences, the VM listener is installed
|
|
* and removed as needed.
|
|
* Listener must be installed before process()
|
|
*/
|
|
vm.state().addListener(this);
|
|
addedListener = true;
|
|
}
|
|
}
|
|
}
|
|
if (info == null) {
|
|
info = JDWP.ObjectReference.MonitorInfo.process(vm, this);
|
|
if (local != null) {
|
|
local.monitorInfo = info;
|
|
if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
|
|
vm.printTrace("ObjectReference " + uniqueID() +
|
|
" temporarily caching monitor info");
|
|
}
|
|
}
|
|
}
|
|
} catch (JDWPException exc) {
|
|
if (exc.errorCode() == JDWP.Error.THREAD_NOT_SUSPENDED) {
|
|
throw new IncompatibleThreadStateException();
|
|
} else {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
return info;
|
|
}
|
|
|
|
public List<ThreadReference> waitingThreads() throws IncompatibleThreadStateException {
|
|
return Arrays.asList((ThreadReference[])jdwpMonitorInfo().waiters);
|
|
}
|
|
|
|
public ThreadReference owningThread() throws IncompatibleThreadStateException {
|
|
return jdwpMonitorInfo().owner;
|
|
}
|
|
|
|
public int entryCount() throws IncompatibleThreadStateException {
|
|
return jdwpMonitorInfo().entryCount;
|
|
}
|
|
|
|
|
|
public List<ObjectReference> referringObjects(long maxReferrers) {
|
|
if (!vm.canGetInstanceInfo()) {
|
|
throw new UnsupportedOperationException(
|
|
"target does not support getting referring objects");
|
|
}
|
|
|
|
if (maxReferrers < 0) {
|
|
throw new IllegalArgumentException("maxReferrers is less than zero: "
|
|
+ maxReferrers);
|
|
}
|
|
|
|
int intMax = (maxReferrers > Integer.MAX_VALUE)?
|
|
Integer.MAX_VALUE: (int)maxReferrers;
|
|
// JDWP can't currently handle more than this (in mustang)
|
|
|
|
try {
|
|
return Arrays.asList((ObjectReference[])JDWP.ObjectReference.ReferringObjects.
|
|
process(vm, this, intMax).referringObjects);
|
|
} catch (JDWPException exc) {
|
|
throw exc.toJDIException();
|
|
}
|
|
}
|
|
|
|
long ref() {
|
|
return ref;
|
|
}
|
|
|
|
boolean isClassObject() {
|
|
/*
|
|
* Don't need to worry about subclasses since java.lang.Class is final.
|
|
*/
|
|
return referenceType().name().equals("java.lang.Class");
|
|
}
|
|
|
|
ValueImpl prepareForAssignmentTo(ValueContainer destination)
|
|
throws InvalidTypeException,
|
|
ClassNotLoadedException {
|
|
|
|
validateAssignment(destination);
|
|
return this; // conversion never necessary
|
|
}
|
|
|
|
void validateAssignment(ValueContainer destination)
|
|
throws InvalidTypeException, ClassNotLoadedException {
|
|
|
|
/*
|
|
* Do these simpler checks before attempting a query of the destination's
|
|
* type which might cause a confusing ClassNotLoadedException if
|
|
* the destination is primitive or an array.
|
|
*/
|
|
/*
|
|
* TO DO: Centralize JNI signature knowledge
|
|
*/
|
|
if (destination.signature().length() == 1) {
|
|
throw new InvalidTypeException("Can't assign object value to primitive");
|
|
}
|
|
if ((destination.signature().charAt(0) == '[') &&
|
|
(type().signature().charAt(0) != '[')) {
|
|
throw new InvalidTypeException("Can't assign non-array value to an array");
|
|
}
|
|
if ("void".equals(destination.typeName())) {
|
|
throw new InvalidTypeException("Can't assign object value to a void");
|
|
}
|
|
|
|
// Validate assignment
|
|
ReferenceType destType = (ReferenceTypeImpl)destination.type();
|
|
ReferenceTypeImpl myType = (ReferenceTypeImpl)referenceType();
|
|
if (!myType.isAssignableTo(destType)) {
|
|
JNITypeParser parser = new JNITypeParser(destType.signature());
|
|
String destTypeName = parser.typeName();
|
|
throw new InvalidTypeException("Can't assign " +
|
|
type().name() +
|
|
" to " + destTypeName);
|
|
}
|
|
}
|
|
|
|
|
|
public String toString() {
|
|
return "instance of " + referenceType().name() + "(id=" + uniqueID() + ")";
|
|
}
|
|
|
|
byte typeValueKey() {
|
|
return JDWP.Tag.OBJECT;
|
|
}
|
|
|
|
private static boolean isNonVirtual(int options) {
|
|
return (options & INVOKE_NONVIRTUAL) != 0;
|
|
}
|
|
}
|