blob: 3672435273ab7fdbdaf2f1ed0b417a496aa5bb8e [file] [log] [blame]
/*
* Copyright (c) 2014, 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.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.InterfaceType;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A supertype for ReferenceTypes allowing method invocations
*/
abstract class InvokableTypeImpl extends ReferenceTypeImpl {
/**
* The invocation result wrapper
* It is necessary because both ClassType and InterfaceType
* use their own type to represent the invocation result
*/
static interface InvocationResult {
ObjectReferenceImpl getException();
ValueImpl getResult();
}
InvokableTypeImpl(VirtualMachine aVm, long aRef) {
super(aVm, aRef);
}
/**
* Method invocation support.
* Shared by ClassType and InterfaceType
* @param threadIntf the thread in which to invoke.
* @param methodIntf method the {@link Method} to invoke.
* @param origArguments the list of {@link Value} arguments bound to the
* invoked method. Values from the list are assigned to arguments
* in the order they appear in the method signature.
* @param options the integer bit flag options.
* @return a {@link Value} mirror of the invoked method's return value.
* @throws java.lang.IllegalArgumentException if the method is not
* a member of this type, if the size of the argument list
* does not match the number of declared arguments for the method, or
* if the method is not static or is a static initializer.
* @throws {@link InvalidTypeException} if any argument in the
* argument list is not assignable to the corresponding method argument
* type.
* @throws ClassNotLoadedException if any argument type has not yet been loaded
* through the appropriate class loader.
* @throws IncompatibleThreadStateException if the specified thread has not
* been suspended by an event.
* @throws InvocationException if the method invocation resulted in
* an exception in the target VM.
* @throws InvalidTypeException If the arguments do not meet this requirement --
* Object arguments must be assignment compatible with the argument
* type. This implies that the argument type must be
* loaded through the enclosing class's class loader.
* Primitive arguments must be either assignment compatible with the
* argument type or must be convertible to the argument type without loss
* of information. See JLS section 5.2 for more information on assignment
* compatibility.
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only - see {@link VirtualMachine#canBeModified()}.
*/
final public Value invokeMethod(ThreadReference threadIntf, Method methodIntf,
List<? extends Value> origArguments, int options)
throws InvalidTypeException,
ClassNotLoadedException,
IncompatibleThreadStateException,
InvocationException {
validateMirror(threadIntf);
validateMirror(methodIntf);
validateMirrorsOrNulls(origArguments);
MethodImpl method = (MethodImpl) methodIntf;
ThreadReferenceImpl thread = (ThreadReferenceImpl) threadIntf;
validateMethodInvocation(method);
List<? extends Value> arguments = method.validateAndPrepareArgumentsForInvoke(origArguments);
ValueImpl[] args = arguments.toArray(new ValueImpl[0]);
InvocationResult ret;
try {
PacketStream stream = sendInvokeCommand(thread, method, args, options);
ret = waitForReply(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 & ClassType.INVOKE_SINGLE_THREADED) == 0) {
vm.notifySuspend();
}
if (ret.getException() != null) {
throw new InvocationException(ret.getException());
} else {
return ret.getResult();
}
}
@Override
boolean isAssignableTo(ReferenceType type) {
ClassTypeImpl superclazz = (ClassTypeImpl) superclass();
if (this.equals(type)) {
return true;
} else if ((superclazz != null) && superclazz.isAssignableTo(type)) {
return true;
} else {
List<InterfaceType> interfaces = interfaces();
Iterator<InterfaceType> iter = interfaces.iterator();
while (iter.hasNext()) {
InterfaceTypeImpl interfaze = (InterfaceTypeImpl) iter.next();
if (interfaze.isAssignableTo(type)) {
return true;
}
}
return false;
}
}
@Override
final void addVisibleMethods(Map<String, Method> methodMap, Set<InterfaceType> seenInterfaces) {
/*
* Add methods from
* parent types first, so that the methods in this class will
* overwrite them in the hash table
*/
Iterator<InterfaceType> iter = interfaces().iterator();
while (iter.hasNext()) {
InterfaceTypeImpl interfaze = (InterfaceTypeImpl) iter.next();
if (!seenInterfaces.contains(interfaze)) {
interfaze.addVisibleMethods(methodMap, seenInterfaces);
seenInterfaces.add(interfaze);
}
}
ClassTypeImpl clazz = (ClassTypeImpl) superclass();
if (clazz != null) {
clazz.addVisibleMethods(methodMap, seenInterfaces);
}
addToMethodMap(methodMap, methods());
}
final void addInterfaces(List<InterfaceType> list) {
List<InterfaceType> immediate = interfaces();
list.addAll(interfaces());
Iterator<InterfaceType> iter = immediate.iterator();
while (iter.hasNext()) {
InterfaceTypeImpl interfaze = (InterfaceTypeImpl) iter.next();
interfaze.addInterfaces(list);
}
ClassTypeImpl superclass = (ClassTypeImpl) superclass();
if (superclass != null) {
superclass.addInterfaces(list);
}
}
/**
* Returns all the implemented interfaces recursively
* @return A list of all the implemented interfaces (recursively)
*/
final List<InterfaceType> getAllInterfaces() {
List<InterfaceType> all = new ArrayList<>();
addInterfaces(all);
return all;
}
/**
* Shared implementation of {@linkplain ClassType#allMethods()} and
* {@linkplain InterfaceType#allMethods()}
* @return A list of all methods (recursively)
*/
public final List<Method> allMethods() {
ArrayList<Method> list = new ArrayList<>(methods());
ClassType clazz = superclass();
while (clazz != null) {
list.addAll(clazz.methods());
clazz = clazz.superclass();
}
/*
* Avoid duplicate checking on each method by iterating through
* duplicate-free allInterfaces() rather than recursing
*/
for (InterfaceType interfaze : getAllInterfaces()) {
list.addAll(interfaze.methods());
}
return list;
}
@Override
final List<ReferenceType> inheritedTypes() {
List<ReferenceType> inherited = new ArrayList<>();
if (superclass() != null) {
inherited.add(0, superclass()); /* insert at front */
}
for (ReferenceType rt : interfaces()) {
inherited.add(rt);
}
return inherited;
}
private PacketStream sendInvokeCommand(final ThreadReferenceImpl thread,
final MethodImpl method,
final ValueImpl[] args,
final int options) {
/*
* Cache the values of args when TRACE_SENDS is enabled, for later printing.
* If not cached, printing causes a remote call while synchronized, and deadlock.
*/
if ((vm.traceFlags & VirtualMachineImpl.TRACE_SENDS) != 0) {
for (ValueImpl arg: args) {
arg.toString();
}
}
CommandSender sender = getInvokeMethodSender(thread, method, args, options);
PacketStream stream;
if ((options & ClassType.INVOKE_SINGLE_THREADED) != 0) {
stream = thread.sendResumingCommand(sender);
} else {
stream = vm.sendResumingCommand(sender);
}
return stream;
}
private void validateMethodInvocation(Method method)
throws InvalidTypeException,
InvocationException {
if (!canInvoke(method)) {
throw new IllegalArgumentException("Invalid method");
}
/*
* Method must be a static and not a static initializer
*/
if (!method.isStatic()) {
throw new IllegalArgumentException("Cannot invoke instance method on a class/interface type");
} else if (method.isStaticInitializer()) {
throw new IllegalArgumentException("Cannot invoke static initializer");
}
}
/**
* A subclass will provide specific {@linkplain CommandSender}
* @param thread the current invocation thread
* @param method the method to invoke
* @param args the arguments to pass to the method
* @param options the integer bit flag options
* @return the specific {@literal CommandSender} instance
*/
abstract CommandSender getInvokeMethodSender(ThreadReferenceImpl thread,
MethodImpl method,
ValueImpl[] args,
int options);
/**
* Waits for the reply to the last sent command
* @param stream the stream to listen for the reply on
* @return the {@linkplain InvocationResult} instance
* @throws JDWPException when something goes wrong in JDWP
*/
abstract InvocationResult waitForReply(PacketStream stream) throws JDWPException;
/**
* Get the {@linkplain ReferenceType} superclass
* @return the superclass or null
*/
abstract ClassType superclass();
/**
* Get the implemented/extended interfaces
* @return the list of implemented/extended interfaces
*/
abstract List<InterfaceType> interfaces();
/**
* Checks the provided method whether it can be invoked
* @param method the method to check
* @return {@code TRUE} if the implementation knows how to invoke the method,
* {@code FALSE} otherwise
*/
abstract boolean canInvoke(Method method);
}