diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java index dcd6ad1189b..1ee1797dc86 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java @@ -373,8 +373,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler { try { - // TODO: move methodhandle lookup to container? - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodHandles.Lookup lookup = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); MethodHandle partialMessageHandler = lookup .findVirtual(MessageHandler.Partial.class, "onMessage", MethodType.methodType(void.class, Object.class, boolean.class)); partialMessageHandler = partialMessageHandler.bindTo(handler); @@ -431,8 +430,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler { try { - // TODO: move MethodHandle lookup to container? - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodHandles.Lookup lookup = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); MethodHandle wholeMsgMethodHandle = lookup.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class)); wholeMsgMethodHandle = wholeMsgMethodHandle.bindTo(handler); diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java index 659fd033a41..5eeea251808 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java @@ -124,7 +124,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory { try { - FILTER_RETURN_TYPE_METHOD = MethodHandles.lookup() + FILTER_RETURN_TYPE_METHOD = getServerMethodHandleLookup() .findVirtual(JavaxWebSocketSession.class, "filterReturnType", MethodType.methodType(void.class, Object.class)); } catch (Throwable e) @@ -322,16 +322,17 @@ public abstract class JavaxWebSocketFrameHandlerFactory try { + MethodHandles.Lookup lookup = getServerMethodHandleLookup(); if (DecodedMessageSink.class.isAssignableFrom(msgMetadata.sinkClass)) { - MethodHandle ctorHandle = MethodHandles.lookup().findConstructor(msgMetadata.sinkClass, + MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass, MethodType.methodType(void.class, CoreSession.class, msgMetadata.registeredDecoder.interfaceType, MethodHandle.class)); Decoder decoder = session.getDecoders().getInstanceOf(msgMetadata.registeredDecoder); return (MessageSink)ctorHandle.invoke(session.getCoreSession(), decoder, msgMetadata.handle); } else { - MethodHandle ctorHandle = MethodHandles.lookup().findConstructor(msgMetadata.sinkClass, + MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass, MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.handle); } @@ -388,7 +389,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory protected JavaxWebSocketFrameHandlerMetadata createEndpointMetadata(Class endpointClass, EndpointConfig endpointConfig) { JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); - MethodHandles.Lookup lookup = getMethodHandleLookup(endpointClass); + MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen", javax.websocket.Session.class, javax.websocket.EndpointConfig.class); @@ -410,7 +411,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory protected JavaxWebSocketFrameHandlerMetadata discoverJavaxFrameHandlerMetadata(Class endpointClass, JavaxWebSocketFrameHandlerMetadata metadata) { - MethodHandles.Lookup lookup = getMethodHandleLookup(endpointClass); + MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); Method onmethod; // OnOpen [0..1] @@ -704,9 +705,53 @@ public abstract class JavaxWebSocketFrameHandlerFactory } } - private MethodHandles.Lookup getMethodHandleLookup(Class endpointClass) + /** + *

+ * Gives a {@link MethodHandles.Lookup} instance to be used to find methods in server classes. + * For lookups on application classes use {@link #getApplicationMethodHandleLookup(Class)} instead. + *

+ *

+ * This uses the caller sensitive {@link MethodHandles#lookup()}, this will allow MethodHandle access + * to server classes we need to use and will give access permissions to private methods as well. + *

+ * + * @return a lookup object to be used to find methods on server classes. + */ + public static MethodHandles.Lookup getServerMethodHandleLookup() { - return MethodHandles.publicLookup().in(endpointClass); + return MethodHandles.lookup(); + } + + /** + *

+ * Gives a {@link MethodHandles.Lookup} instance to be used to find public methods in application classes. + * For lookups on server classes use {@link #getServerMethodHandleLookup()} instead. + *

+ *

+ * This uses {@link MethodHandles#publicLookup()} as we only need access to public method of the lookupClass. + * To look up a method on the lookupClass, it must be public and the class must be accessible from this + * module, so if the lookupClass is in a JPMS module it must be exported so that the public methods + * of the lookupClass are accessible outside of the module. + *

+ *

+ * The {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} allows us to search specifically + * in the endpoint Class to avoid any potential linkage errors which could occur if the same + * class is present in multiple web apps. Unlike using {@link MethodHandles#publicLookup()} + * using {@link MethodHandles#lookup()} with {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} + * will cause the lookup to lose its public access to the lookup class if they are in different modules. + *

+ *

+ * {@link MethodHandles#privateLookupIn(Class, MethodHandles.Lookup)} is also unsuitable because it + * requires the caller module to read the target module, and the target module to open reflective + * access to the lookupClasses private methods. This is possible but requires extra configuration + * to provide private access which is not necessary for the purpose of accessing the public methods. + *

+ * @param lookupClass the desired lookup class for the new lookup object. + * @return a lookup object to be used to find methods on the lookupClass. + */ + public static MethodHandles.Lookup getApplicationMethodHandleLookup(Class lookupClass) + { + return MethodHandles.publicLookup().in(lookupClass); } private static class DecodedArgs diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/messages/DecodedBinaryMessageSink.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/messages/DecodedBinaryMessageSink.java index dae52a2e198..7932300479a 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/messages/DecodedBinaryMessageSink.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/messages/DecodedBinaryMessageSink.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.websocket.javax.common.messages; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.nio.ByteBuffer; import javax.websocket.CloseReason; @@ -28,6 +27,7 @@ import javax.websocket.Decoder; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.exception.CloseException; +import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink; import org.eclipse.jetty.websocket.util.messages.MessageSink; @@ -44,7 +44,7 @@ public class DecodedBinaryMessageSink extends DecodedMessageSink extends DecodedMessageSink extends DecodedMessageSink extends DecodedMessageSink refc = copy.getClass(); String name = "accept"; MethodType methodType = MethodType.methodType(void.class, type); - MethodHandle handle = MethodHandles.lookup().findVirtual(refc, name, methodType); + MethodHandle handle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(refc, name, methodType); return handle.bindTo(copy); } catch (NoSuchMethodException | IllegalAccessException e) diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CompletableFutureMethodHandle.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CompletableFutureMethodHandle.java index 3924b2a6552..c7008dea780 100644 --- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CompletableFutureMethodHandle.java +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/CompletableFutureMethodHandle.java @@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.concurrent.CompletableFuture; +import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.util.InvokerUtils; import org.eclipse.jetty.websocket.util.ReflectUtils; @@ -31,7 +32,8 @@ public class CompletableFutureMethodHandle public static MethodHandle of(Class type, CompletableFuture future) { Method method = ReflectUtils.findMethod(CompletableFuture.class, "complete", type); - MethodHandle completeHandle = InvokerUtils.mutatedInvoker(MethodHandles.lookup(), CompletableFuture.class, method, new InvokerUtils.Arg(type)); + MethodHandles.Lookup lookup = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); + MethodHandle completeHandle = InvokerUtils.mutatedInvoker(lookup, CompletableFuture.class, method, new InvokerUtils.Arg(type)); return completeHandle.bindTo(future); } } diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index ef001076b9b..13a22ab6d0f 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -157,7 +157,8 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle try { - MethodHandle ctorHandle = MethodHandles.lookup().findConstructor(sinkClass, + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); + MethodHandle ctorHandle = lookup.findConstructor(sinkClass, MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgHandle); } @@ -194,7 +195,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); - MethodHandles.Lookup lookup = getMethodHandleLookup(endpointClass); + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getApplicationMethodHandleLookup(endpointClass); Method openMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketConnect", Session.class); MethodHandle open = toMethodHandle(lookup, openMethod); @@ -273,7 +274,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle metadata.setIdleTimeout(Duration.ofMillis(max)); metadata.setBatchMode(anno.batchMode()); - MethodHandles.Lookup lookup = getMethodHandleLookup(endpointClass); + MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); Method onmethod; // OnWebSocketConnect [0..1] @@ -456,9 +457,53 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle throw new InvalidSignatureException(err.toString()); } - private MethodHandles.Lookup getMethodHandleLookup(Class endpointClass) + /** + *

+ * Gives a {@link MethodHandles.Lookup} instance to be used to find methods in server classes. + * For lookups on application classes use {@link #getApplicationMethodHandleLookup(Class)} instead. + *

+ *

+ * This uses the caller sensitive {@link MethodHandles#lookup()}, this will allow MethodHandle access + * to server classes we need to use and will give access permissions to private methods as well. + *

+ * + * @return a lookup object to be used to find methods on server classes. + */ + public static MethodHandles.Lookup getServerMethodHandleLookup() { - return MethodHandles.publicLookup().in(endpointClass); + return MethodHandles.lookup(); + } + + /** + *

+ * Gives a {@link MethodHandles.Lookup} instance to be used to find public methods in application classes. + * For lookups on server classes use {@link #getServerMethodHandleLookup()} instead. + *

+ *

+ * This uses {@link MethodHandles#publicLookup()} as we only need access to public method of the lookupClass. + * To look up a method on the lookupClass, it must be public and the class must be accessible from this + * module, so if the lookupClass is in a JPMS module it must be exported so that the public methods + * of the lookupClass are accessible outside of the module. + *

+ *

+ * The {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} allows us to search specifically + * in the endpoint Class to avoid any potential linkage errors which could occur if the same + * class is present in multiple web apps. Unlike using {@link MethodHandles#publicLookup()} + * using {@link MethodHandles#lookup()} with {@link java.lang.invoke.MethodHandles.Lookup#in(Class)} + * will cause the lookup to lose its public access to the lookup class if they are in different modules. + *

+ *

+ * {@link MethodHandles#privateLookupIn(Class, MethodHandles.Lookup)} is also unsuitable because it + * requires the caller module to read the target module, and the target module to open reflective + * access to the lookupClasses private methods. This is possible but requires extra configuration + * to provide private access which is not necessary for the purpose of accessing the public methods. + *

+ * @param lookupClass the desired lookup class for the new lookup object. + * @return a lookup object to be used to find methods on the lookupClass. + */ + public static MethodHandles.Lookup getApplicationMethodHandleLookup(Class lookupClass) + { + return MethodHandles.publicLookup().in(lookupClass); } @Override diff --git a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java index 7976f7d66eb..366827f169f 100644 --- a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java +++ b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java @@ -52,7 +52,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes public OutgoingMessageCapture() { - MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getApplicationMethodHandleLookup(this.getClass()); try { MethodHandle text = lookup.findVirtual(this.getClass(), "onWholeText", MethodType.methodType(Void.TYPE, String.class));