Fixed RMI bug with overriding methods w/ connection as first parameter
This commit is contained in:
parent
1d3531afc0
commit
80b358f8aa
@ -48,7 +48,7 @@ class AsmCachedMethod extends CachedMethod {
|
|||||||
public
|
public
|
||||||
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
||||||
try {
|
try {
|
||||||
if (origMethod == null) {
|
if (origMethod == method) {
|
||||||
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
|
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -41,6 +41,8 @@ import com.esotericsoftware.reflectasm.MethodAccess;
|
|||||||
import dorkbox.network.connection.Connection;
|
import dorkbox.network.connection.Connection;
|
||||||
import dorkbox.network.connection.EndPoint;
|
import dorkbox.network.connection.EndPoint;
|
||||||
import dorkbox.util.ClassHelper;
|
import dorkbox.util.ClassHelper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@ -50,10 +52,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
public
|
public
|
||||||
class CachedMethod {
|
class CachedMethod {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CachedMethod.class);
|
||||||
|
|
||||||
// not concurrent because they are setup during system initialization
|
// not concurrent because they are setup during system initialization
|
||||||
public static final Map<Class<?>, Class<?>> overriddenMethods = new HashMap<Class<?>, Class<?>>();
|
public static final Map<Class<?>, Class<?>> overriddenMethods = new HashMap<Class<?>, Class<?>>();
|
||||||
public static final Map<Class<?>, Class<?>> overriddenReverseMethods = new HashMap<Class<?>, Class<?>>();
|
public static final Map<Class<?>, Class<?>> overriddenReverseMethods = new HashMap<Class<?>, Class<?>>();
|
||||||
|
|
||||||
|
// the purpose of the method cache, is to accelerate looking up methods for specific class
|
||||||
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
|
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
|
||||||
|
|
||||||
// type will be likely be the interface
|
// type will be likely be the interface
|
||||||
@ -81,8 +86,12 @@ class CachedMethod {
|
|||||||
// This MUST hold valid for both remote and local connection types.
|
// This MUST hold valid for both remote and local connection types.
|
||||||
|
|
||||||
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
||||||
// interface as the first parameter.
|
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Map<Method, Method> overriddenMethods = getOverriddenMethods(type, methods);
|
Map<Method, Method> overriddenMethods = getOverriddenMethods(type, methods);
|
||||||
|
final boolean hasOverriddenMethods = !overriddenMethods.isEmpty();
|
||||||
|
|
||||||
|
|
||||||
MethodAccess methodAccess = null;
|
MethodAccess methodAccess = null;
|
||||||
@ -99,30 +108,36 @@ class CachedMethod {
|
|||||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
Class<?>[] asmParameterTypes = parameterTypes;
|
Class<?>[] asmParameterTypes = parameterTypes;
|
||||||
|
|
||||||
Method overriddenMethod = overriddenMethods.remove(method);
|
if (hasOverriddenMethods) {
|
||||||
boolean overridden = overriddenMethod != null;
|
Method overriddenMethod = overriddenMethods.remove(method);
|
||||||
if (overridden) {
|
boolean overridden = overriddenMethod != null;
|
||||||
// we can override the details of this method BECAUSE (and only because) our kryo registration override will return
|
if (overridden) {
|
||||||
// the correct object for this overridden method to be called on.
|
// we can override the details of this method BECAUSE (and only because) our kryo registration override will return
|
||||||
method = overriddenMethod;
|
// the correct object for this overridden method to be called on.
|
||||||
|
method = overriddenMethod;
|
||||||
|
|
||||||
Class<?> overrideType = method.getDeclaringClass();
|
Class<?> overrideType = method.getDeclaringClass();
|
||||||
|
|
||||||
if (kryo.getAsmEnabled() && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
|
if (kryo.getAsmEnabled() && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
|
||||||
localMethodAccess = MethodAccess.get(overrideType);
|
localMethodAccess = MethodAccess.get(overrideType);
|
||||||
asmParameterTypes = method.getParameterTypes();
|
asmParameterTypes = method.getParameterTypes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedMethod cachedMethod = null;
|
CachedMethod cachedMethod = null;
|
||||||
if (localMethodAccess != null) {
|
if (localMethodAccess != null) {
|
||||||
try {
|
try {
|
||||||
|
final int index = localMethodAccess.getIndex(method.getName(), asmParameterTypes);
|
||||||
|
|
||||||
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
|
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
|
||||||
asmCachedMethod.methodAccessIndex = localMethodAccess.getIndex(method.getName(), asmParameterTypes);
|
asmCachedMethod.methodAccessIndex = index;
|
||||||
asmCachedMethod.methodAccess = localMethodAccess;
|
asmCachedMethod.methodAccess = localMethodAccess;
|
||||||
cachedMethod = asmCachedMethod;
|
cachedMethod = asmCachedMethod;
|
||||||
} catch (RuntimeException ignored) {
|
} catch (RuntimeException e) {
|
||||||
ignored.printStackTrace();
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Unable to use ReflectAsm for {}:{}", method.getDeclaringClass(), method.getName(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,8 +177,8 @@ class CachedMethod {
|
|||||||
|
|
||||||
for (Method origMethod : origMethods) {
|
for (Method origMethod : origMethods) {
|
||||||
String name = origMethod.getName();
|
String name = origMethod.getName();
|
||||||
Class<?>[] types = origMethod.getParameterTypes();
|
Class<?>[] origTypes = origMethod.getParameterTypes();
|
||||||
int modLength = types.length + 1;
|
int origLength = origTypes.length + 1;
|
||||||
|
|
||||||
METHOD_CHECK:
|
METHOD_CHECK:
|
||||||
for (Method implMethod : implMethods) {
|
for (Method implMethod : implMethods) {
|
||||||
@ -171,18 +186,24 @@ class CachedMethod {
|
|||||||
Class<?>[] checkTypes = implMethod.getParameterTypes();
|
Class<?>[] checkTypes = implMethod.getParameterTypes();
|
||||||
int checkLength = checkTypes.length;
|
int checkLength = checkTypes.length;
|
||||||
|
|
||||||
if (modLength != checkLength || !(name.equals(checkName))) {
|
if (origLength != checkLength || !(name.equals(checkName))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkLength > 0
|
// checkLength > 0
|
||||||
Class<?> checkType = checkTypes[0];
|
Class<?> shouldBeConnectionType = checkTypes[0];
|
||||||
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, checkType)) {
|
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
|
||||||
// now we check to see if our "check" method is equal to our "cached" method + Connection
|
// now we check to see if our "check" method is equal to our "cached" method + Connection
|
||||||
for (int k = 1; k < checkLength; k++) {
|
if (checkLength == 1) {
|
||||||
if (types[k - 1] == checkTypes[k]) {
|
overrideMap.put(origMethod, implMethod);
|
||||||
overrideMap.put(origMethod, implMethod);
|
break;
|
||||||
break METHOD_CHECK;
|
}
|
||||||
|
else {
|
||||||
|
for (int k = 1; k < checkLength; k++) {
|
||||||
|
if (origTypes[k - 1] == checkTypes[k]) {
|
||||||
|
overrideMap.put(origMethod, implMethod);
|
||||||
|
break METHOD_CHECK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,7 +305,7 @@ class CachedMethod {
|
|||||||
public
|
public
|
||||||
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
||||||
// did we override our cached method?
|
// did we override our cached method?
|
||||||
if (origMethod == null) {
|
if (method == origMethod) {
|
||||||
return this.method.invoke(target, args);
|
return this.method.invoke(target, args);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -80,7 +80,7 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
private boolean udt;
|
private boolean udt;
|
||||||
|
|
||||||
private Byte lastResponseID;
|
private Byte lastResponseID;
|
||||||
private byte nextResponseId = 1;
|
private byte nextResponseId = (byte) 1;
|
||||||
|
|
||||||
public
|
public
|
||||||
RemoteInvocationHandler(final Connection connection, final int objectID) {
|
RemoteInvocationHandler(final Connection connection, final int objectID) {
|
||||||
@ -126,7 +126,7 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
.add(this.responseListener);
|
.add(this.responseListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"AutoUnboxing", "AutoBoxing"})
|
@SuppressWarnings({"AutoUnboxing", "AutoBoxing", "NumericCastThatLosesPrecision"})
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
|
Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
|
||||||
@ -196,7 +196,7 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
|
|
||||||
final Logger logger1 = RemoteInvocationHandler.logger;
|
final Logger logger1 = RemoteInvocationHandler.logger;
|
||||||
|
|
||||||
EndPoint<Connection> endPoint = this.connection.getEndPoint();
|
EndPoint endPoint = this.connection.getEndPoint();
|
||||||
final CryptoSerializationManager serializationManager = endPoint.getSerialization();
|
final CryptoSerializationManager serializationManager = endPoint.getSerialization();
|
||||||
|
|
||||||
InvokeMethod invokeMethod = new InvokeMethod();
|
InvokeMethod invokeMethod = new InvokeMethod();
|
||||||
@ -224,17 +224,20 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
|
|
||||||
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||||
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||||
// an implementation, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||||
// the interface, and the implementation may override the method, so that we add the connection as the first in
|
// the interface, and the implType may override the method, so that we add the connection as the first in
|
||||||
// the list of parameters.
|
// the list of parameters.
|
||||||
//
|
//
|
||||||
// for example:
|
// for example:
|
||||||
// Interface: foo(String x)
|
// Interface: foo(String x)
|
||||||
// Impl: foo(Connection c, String x)
|
// Impl: foo(Connection c, String x)
|
||||||
//
|
//
|
||||||
// The implementation (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
|
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
|
||||||
// This MUST hold valid for both remote and local connection types.
|
// This MUST hold valid for both remote and local connection types.
|
||||||
|
|
||||||
|
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
||||||
|
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
|
||||||
|
|
||||||
if (checkMethod.equals(method)) {
|
if (checkMethod.equals(method)) {
|
||||||
invokeMethod.cachedMethod = cachedMethod;
|
invokeMethod.cachedMethod = cachedMethod;
|
||||||
break;
|
break;
|
||||||
@ -249,28 +252,28 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
|
|
||||||
// An invocation doesn't need a response is if it's async and no return values or exceptions are wanted back.
|
// An invocation doesn't need a response is if it's async and no return values or exceptions are wanted back.
|
||||||
boolean needsResponse = !this.udp && (this.transmitReturnValue || this.transmitExceptions || !this.nonBlocking);
|
boolean needsResponse = !this.udp && (this.transmitReturnValue || this.transmitExceptions || !this.nonBlocking);
|
||||||
byte responseID = 0;
|
byte responseID = (byte) 0;
|
||||||
if (needsResponse) {
|
if (needsResponse) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// Increment the response counter and put it into the low bits of the responseID.
|
// Increment the response counter and put it into the low bits of the responseID.
|
||||||
responseID = this.nextResponseId++;
|
responseID = this.nextResponseId++;
|
||||||
if (this.nextResponseId > RmiBridge.responseIdMask) {
|
if (this.nextResponseId > RmiBridge.responseIdMask) {
|
||||||
this.nextResponseId = 1;
|
this.nextResponseId = (byte) 1;
|
||||||
}
|
}
|
||||||
this.pendingResponses[responseID] = true;
|
this.pendingResponses[responseID] = true;
|
||||||
}
|
}
|
||||||
// Pack other data into the high bits.
|
// Pack other data into the high bits.
|
||||||
byte responseData = responseID;
|
byte responseData = responseID;
|
||||||
if (this.transmitReturnValue) {
|
if (this.transmitReturnValue) {
|
||||||
responseData |= RmiBridge.returnValueMask;
|
responseData |= (byte) RmiBridge.returnValueMask;
|
||||||
}
|
}
|
||||||
if (this.transmitExceptions) {
|
if (this.transmitExceptions) {
|
||||||
responseData |= RmiBridge.returnExceptionMask;
|
responseData |= (byte) RmiBridge.returnExceptionMask;
|
||||||
}
|
}
|
||||||
invokeMethod.responseData = responseData;
|
invokeMethod.responseData = responseData;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
invokeMethod.responseData = 0; // A response data of 0 means to not respond.
|
invokeMethod.responseData = (byte) 0; // A response data of 0 means to not respond.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.udp) {
|
if (this.udp) {
|
||||||
@ -314,13 +317,13 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
if (returnType == float.class) {
|
if (returnType == float.class) {
|
||||||
return 0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
if (returnType == char.class) {
|
if (returnType == char.class) {
|
||||||
return (char) 0;
|
return (char) 0;
|
||||||
}
|
}
|
||||||
if (returnType == long.class) {
|
if (returnType == long.class) {
|
||||||
return 0l;
|
return 0L;
|
||||||
}
|
}
|
||||||
if (returnType == short.class) {
|
if (returnType == short.class) {
|
||||||
return (short) 0;
|
return (short) 0;
|
||||||
@ -329,7 +332,7 @@ class RemoteInvocationHandler implements InvocationHandler {
|
|||||||
return (byte) 0;
|
return (byte) 0;
|
||||||
}
|
}
|
||||||
if (returnType == double.class) {
|
if (returnType == double.class) {
|
||||||
return 0d;
|
return 0.0d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -70,11 +70,17 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
|||||||
* the connection reference. The remote side (that initiates the RMI calls), MUST use the interface, and the implementation may override the
|
* the connection reference. The remote side (that initiates the RMI calls), MUST use the interface, and the implementation may override the
|
||||||
* method, so that we add the connection as the first in the list of parameters.
|
* method, so that we add the connection as the first in the list of parameters.
|
||||||
* <p/>
|
* <p/>
|
||||||
* for example: Interface: foo(String x) Impl: foo(Connection c, String x)
|
* for example:
|
||||||
|
* Interface: foo(String x)
|
||||||
|
* Impl: foo(Connection c, String x)
|
||||||
* <p/>
|
* <p/>
|
||||||
* The implementation (if it exists, with the same name, and with the same signature+connection) will be called from the interface. This
|
* The implementation (if it exists, with the same name, and with the same signature+connection) will be called from the interface. This
|
||||||
* MUST hold valid for both remote and local connection types.
|
* MUST hold valid for both remote and local connection types.
|
||||||
*
|
*
|
||||||
|
* To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
||||||
|
* interface as the first parameter, and CachedMethod.registerOverridden(ifaceClass, implClass) must be called.
|
||||||
|
*
|
||||||
|
*
|
||||||
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
|
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
@ -227,6 +233,7 @@ class RmiBridge {
|
|||||||
* @param connection
|
* @param connection
|
||||||
* The remote side of this connection requested the invocation.
|
* The remote side of this connection requested the invocation.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("NumericCastThatLosesPrecision")
|
||||||
protected
|
protected
|
||||||
void invoke(final Connection connection, final Object target, final InvokeMethod invokeMethod) throws IOException {
|
void invoke(final Connection connection, final Object target, final InvokeMethod invokeMethod) throws IOException {
|
||||||
CachedMethod cachedMethod = invokeMethod.cachedMethod;
|
CachedMethod cachedMethod = invokeMethod.cachedMethod;
|
||||||
|
Loading…
Reference in New Issue
Block a user