/* |
|
* Copyright (c) 2014, 2018, 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) { |
|
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); |
|
} |