From c60947a00bef2e130ccbd3cf68cff313d98f5e1f Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 28 Aug 2024 02:18:57 +0200 Subject: [PATCH 1/4] JPMS improvements: use JPMS Lookup class where applicable The best way to define a class is to define using an anchor class, which defines its classloader and security policies, and is the only way to define a class in Java 9+ without --add-opens on the command line. The package name of the anchor class must be the same as the new class. If the conditions are not met, we fall back to calling private methods on the ClassLoader to define classes. This needs --add-opens, otherwise a exception is raised. Moved global references to the private ClassLoader methods to a separate ClassLoaderMethods class. This means that the global references will be initialized lazily only when needed, to avoid getting an exception on accessing the private methods (if not --add-opens, e.g. in GF Embedded) if they are not needed. --- .../com/sun/ejb/codegen/ClassGenerator.java | 72 ++++++++++++------- .../sun/ejb/codegen/ClassLoaderMethods.java | 66 +++++++++++++++++ .../ejb/codegen/EjbOptionalIntfGenerator.java | 15 ++-- .../sun/ejb/codegen/GenericHomeGenerator.java | 2 +- .../com/sun/ejb/containers/BaseContainer.java | 12 ++-- .../JavaEEInterceptorBuilderFactoryImpl.java | 6 +- .../test/java/com/sun/ejb/EJBUtilsTest.java | 2 +- .../ejb/mdb/MessageBeanContainer.java | 2 +- .../mdb/MessageBeanInterfaceGenerator.java | 8 +-- .../weld/services/ProxyServicesImpl.java | 39 +++------- 10 files changed, 144 insertions(+), 80 deletions(-) create mode 100644 appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassLoaderMethods.java diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java index e43b78254b2..6b729bd898e 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java @@ -20,11 +20,9 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.security.AccessController; import java.security.PrivilegedAction; -import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.util.Objects; import java.util.logging.Logger; @@ -46,27 +44,27 @@ public final class ClassGenerator { private static final Logger LOG = Logger.getLogger(ClassGenerator.class.getName()); - private static Method defineClassMethod; - private static Method defineClassMethodSM; - - static { - try { - final PrivilegedExceptionAction action = () -> { - final Class cl = Class.forName("java.lang.ClassLoader"); - final String name = "defineClass"; - defineClassMethod = cl.getDeclaredMethod(name, String.class, byte[].class, int.class, int.class); - defineClassMethod.setAccessible(true); - defineClassMethodSM = cl.getDeclaredMethod( - name, String.class, byte[].class, int.class, int.class, ProtectionDomain.class); - defineClassMethodSM.setAccessible(true); - return null; - }; - AccessController.doPrivileged(action); - LOG.config("ClassLoader methods capable of generating classes were successfully detected."); - } catch (final Exception e) { - throw new Error("Could not initialize access to ClassLoader.defineClass method.", e); - } - } +// private static Method defineClassMethod; +// private static Method defineClassMethodSM; +// +// static { +// try { +// final PrivilegedExceptionAction action = () -> { +// final Class cl = Class.forName("java.lang.ClassLoader"); +// final String name = "defineClass"; +// defineClassMethod = cl.getDeclaredMethod(name, String.class, byte[].class, int.class, int.class); +// ClassLoaderMethods.defineClassMethod.setAccessible(true); +// defineClassMethodSM = cl.getDeclaredMethod( +// name, String.class, byte[].class, int.class, int.class, ProtectionDomain.class); +// ClassLoaderMethods.defineClassMethodSM.setAccessible(true); +// return null; +// }; +// AccessController.doPrivileged(action); +// LOG.config("ClassLoader methods capable of generating classes were successfully detected."); +// } catch (final Exception e) { +// throw new Error("Could not initialize access to ClassLoader.defineClass method.", e); +// } +// } private ClassGenerator() { @@ -103,12 +101,17 @@ public static Class defineClass(final ClassLoader loader, final Class anch /** * Calls the {@link Lookup}'s defineClass method to create a new class. * + * In most cases, use {@link #defineClass(java.lang.ClassLoader, java.lang.Class, java.lang.String, java.lang.String, byte[])} + * instead. That method is safe to use even in cases that are not compatible with the Java module system. + * This method should be called only if the packages of `anchorClass` and `className` are the same. + * * @param anchorClass the class used as an "orientation" class. See the {@link Lookup} class for more info. - * @param className expected binary name or null + * @param className expected binary name or null. Must have the same package as `anchorClass` * @param classData the valid bytes that make up the class data. * @return the new generated class * @throws ClassDefinitionException invalid data, missing dependency, or another error related * to the class generation + * */ public static Class defineClass(final Class anchorClass, final String className, final byte[] classData) { LOG.log(CONFIG, "Defining class: {0} with anchorClass: {1}", new Object[] {className, anchorClass}); @@ -133,7 +136,12 @@ public static Class defineClass(final Class anchorClass, final String clas * @return the new generated class * @throws ClassDefinitionException invalid data, missing dependency, or another error related * to the class generation + * + * @deprecated Use {@link #defineClass(java.lang.ClassLoader, java.lang.Class, java.lang.String, java.lang.String, byte[])} + * or {@link #defineClass(java.lang.Class, java.lang.String, byte[])} methods instead. + * Those methods support the Java Module system. */ + @Deprecated public static Class defineClass(final ClassLoader loader, final String className, final byte[] classData) throws ClassDefinitionException { return defineClass(loader, className, classData, 0, classData.length); @@ -151,13 +159,17 @@ public static Class defineClass(final ClassLoader loader, final String classN * @return the new generated class * @throws ClassDefinitionException invalid data, missing dependency, or another error related * to the class generation + * @deprecated Use {@link #defineClass(java.lang.ClassLoader, java.lang.Class, java.lang.String, java.lang.String, byte[])} + * or {@link #defineClass(java.lang.Class, java.lang.String, byte[])} methods instead. + * Those methods support the Java Module system. */ + @Deprecated public static Class defineClass(final ClassLoader loader, final String className, final byte[] classData, final int offset, final int length) throws ClassDefinitionException { LOG.log(CONFIG, "Defining class: {0} by loader: {1}", new Object[] {className, loader}); final PrivilegedAction> action = () -> { try { - return (Class) defineClassMethod.invoke(loader, className, classData, 0, length); + return (Class) ClassLoaderMethods.defineClassMethod.invoke(loader, className, classData, 0, length); } catch (final Exception | NoClassDefFoundError | ClassFormatError e) { throw new ClassDefinitionException(className, loader, e); } @@ -176,7 +188,11 @@ public static Class defineClass(final ClassLoader loader, final String classN * @return the new generated class * @throws ClassDefinitionException invalid data, missing dependency, or another error related * to the class generation + * @deprecated Use {@link #defineClass(java.lang.ClassLoader, java.lang.Class, java.lang.String, java.lang.String, byte[])} + * or {@link #defineClass(java.lang.Class, java.lang.String, byte[])} methods instead. + * Those methods support the Java Module system. */ + @Deprecated public static Class defineClass(final ClassLoader loader, final String className, final byte[] classData, final ProtectionDomain protectionDomain) throws ClassDefinitionException { return defineClass(loader, className, classData, 0, classData.length, protectionDomain); @@ -195,7 +211,11 @@ public static Class defineClass(final ClassLoader loader, final String classN * @return the new generated class * @throws ClassDefinitionException invalid data, missing dependency, or another error related * to the class generation + * @deprecated Use {@link #defineClass(java.lang.ClassLoader, java.lang.Class, java.lang.String, java.lang.String, byte[])} + * or {@link #defineClass(java.lang.Class, java.lang.String, byte[])} methods instead. + * Those methods support the Java Module system. */ + @Deprecated public static Class defineClass( final ClassLoader loader, final String className, final byte[] classData, final int offset, final int length, @@ -203,7 +223,7 @@ public static Class defineClass( LOG.log(CONFIG, "Defining class: {0} by loader: {1}", new Object[] {className, loader}); final PrivilegedAction> action = () -> { try { - return (Class) defineClassMethodSM.invoke(loader, className, classData, 0, length, protectionDomain); + return (Class) ClassLoaderMethods.defineClassMethodSM.invoke(loader, className, classData, 0, length, protectionDomain); } catch (final Exception | NoClassDefFoundError | ClassFormatError e) { throw new ClassDefinitionException(className, loader, e); } diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassLoaderMethods.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassLoaderMethods.java new file mode 100644 index 00000000000..79d450e8d0a --- /dev/null +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassLoaderMethods.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package com.sun.ejb.codegen; + +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.logging.Logger; + +/** + * Lazily loaded classloader methods. + * + * They are loaded only if needed. If they are not needed, loading them would cause {@link InaccessibleObjectException} + * due to the module system encapsulation. In that case, {@code --add-opens=java.base/java.lang=ALL-UNNAMED} is needed + * in the JVM arguments. + * + * @author Ondro Mihalyi + */ +class ClassLoaderMethods { + + private static final Logger LOG = Logger.getLogger(ClassGenerator.class.getName()); + + private ClassLoaderMethods() { + // hidden + } + + static Method defineClassMethod; + static Method defineClassMethodSM; + + static { + try { + final PrivilegedExceptionAction action = () -> { + final Class cl = Class.forName("java.lang.ClassLoader"); + final String name = "defineClass"; + defineClassMethod = cl.getDeclaredMethod(name, String.class, byte[].class, int.class, int.class); + defineClassMethod.setAccessible(true); + defineClassMethodSM = cl.getDeclaredMethod( + name, String.class, byte[].class, int.class, int.class, ProtectionDomain.class); + defineClassMethodSM.setAccessible(true); + return null; + }; + AccessController.doPrivileged(action); + LOG.config("ClassLoader methods capable of generating classes were successfully detected."); + } catch (final InaccessibleObjectException e) { + throw new Error("Could not access ClassLoader.defineClass method. Try adding --add-opens=java.base/java.lang=ALL-UNNAMED on the JVM command line.", e); + } catch (final Exception e) { + throw new Error("Could not initialize access to ClassLoader.defineClass method.", e); + } + } + +} diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/EjbOptionalIntfGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/EjbOptionalIntfGenerator.java index d3f933c5690..84fb2b198be 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/EjbOptionalIntfGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/EjbOptionalIntfGenerator.java @@ -66,26 +66,25 @@ public class EjbOptionalIntfGenerator extends BeanGeneratorBase { private static final String DELEGATE_FIELD_NAME = "__ejb31_delegate"; private final Map classMap = new HashMap<>(); - private final ClassLoader loader; private ProtectionDomain protectionDomain; - public EjbOptionalIntfGenerator(ClassLoader loader) { - this.loader = loader; + public EjbOptionalIntfGenerator() { } - public Class loadClass(final String name) throws ClassNotFoundException { + public Class loadClass(final String className, final Class anchorClass) throws ClassNotFoundException { Class clz = null; + ClassLoader loader = anchorClass.getClassLoader(); try { - clz = loader.loadClass(name); + clz = loader.loadClass(className); } catch (ClassNotFoundException cnfe) { - final byte[] classData = classMap.get(name); + final byte[] classData = classMap.get(className); if (classData != null) { - PrivilegedAction> action = () -> defineClass(loader, name, classData, protectionDomain); + PrivilegedAction> action = () -> defineClass(loader, anchorClass, anchorClass.getPackageName(), className, classData); clz = AccessController.doPrivileged(action); } } if (clz == null) { - throw new ClassNotFoundException(name); + throw new ClassNotFoundException(className); } return clz; } diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/GenericHomeGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/GenericHomeGenerator.java index 2da65d57617..0407a709b3f 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/GenericHomeGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/GenericHomeGenerator.java @@ -49,7 +49,7 @@ public class GenericHomeGenerator extends Generator { public GenericHomeGenerator(final ClassLoader loader, final Class anchorClass) { super(loader); this.anchorClass = anchorClass; - this.packageName = getClass().getPackageName(); + this.packageName = anchorClass.getPackageName(); } diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/BaseContainer.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/BaseContainer.java index d5668693f04..ca8b260ffa7 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/BaseContainer.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/BaseContainer.java @@ -641,9 +641,9 @@ protected BaseContainer(final ContainerType type, final EjbDescriptor ejbDesc, f addToGeneratedMonitoredMethodInfo(clz); this.optIntfClassName = EJBUtils.getGeneratedOptionalInterfaceName(ejbClass.getName()); - optIntfClassLoader = new EjbOptionalIntfGenerator(loader); + optIntfClassLoader = new EjbOptionalIntfGenerator(); optIntfClassLoader.generateOptionalLocalInterface(ejbClass, optIntfClassName); - ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optIntfClassName); + ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optIntfClassName, ejbClass); } if (isStatelessSession || isSingleton) { @@ -1321,7 +1321,7 @@ protected void initializeHome() throws Exception { ejbOptionalLocalBusinessHomeProxyInterfaces[0] = IndirectlySerializable.class; String optionalIntfName = EJBUtils.getGeneratedOptionalInterfaceName(ejbClass.getName()); - ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optionalIntfName); + ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optionalIntfName, ejbClass); ejbOptionalLocalBusinessHomeProxyInterfaces[1] = ejbGeneratedOptionalLocalBusinessIntfClass; // Portable JNDI name for no-interface view. @@ -3059,7 +3059,7 @@ protected void addLocalRemoteInvocationInfo() throws Exception { // Process generated Optional Local Business interface String optClassName = EJBUtils.getGeneratedOptionalInterfaceName(ejbClass.getName()); - ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optClassName); + ejbGeneratedOptionalLocalBusinessIntfClass = optIntfClassLoader.loadClass(optClassName, ejbClass); Method[] methods = ejbGeneratedOptionalLocalBusinessIntfClass.getMethods(); for (Method method : methods) { addInvocationInfo(method, MethodDescriptor.EJB_LOCAL, ejbGeneratedOptionalLocalBusinessIntfClass, false, true); @@ -3342,9 +3342,9 @@ protected EJBLocalObjectImpl instantiateOptionalEJBLocalBusinessObjectImpl() thr optIntfClassLoader.generateOptionalLocalInterfaceSubClass(ejbClass, beanSubClassName, ejbGeneratedOptionalLocalBusinessIntfClass); - optIntfClassLoader.loadClass(ejbGeneratedOptionalLocalBusinessIntfClass.getName()); + optIntfClassLoader.loadClass(ejbGeneratedOptionalLocalBusinessIntfClass.getName(), ejbClass); - Class subClass = optIntfClassLoader.loadClass(beanSubClassName); + Class subClass = optIntfClassLoader.loadClass(beanSubClassName, ejbClass); OptionalLocalInterfaceProvider provider = (OptionalLocalInterfaceProvider) subClass.getConstructor().newInstance(); provider.setOptionalLocalIntfProxy(proxy); localBusinessObjImpl.mapClientObject(ejbClass.getName(), provider); diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/interceptors/JavaEEInterceptorBuilderFactoryImpl.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/interceptors/JavaEEInterceptorBuilderFactoryImpl.java index 1cd29ae9229..fcf513c100a 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/interceptors/JavaEEInterceptorBuilderFactoryImpl.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/containers/interceptors/JavaEEInterceptorBuilderFactoryImpl.java @@ -44,9 +44,9 @@ public JavaEEInterceptorBuilder createBuilder(InterceptorInfo info) throws Excep // in order to create a dynamic proxy String subClassInterfaceName = getGeneratedOptionalInterfaceName(targetObjectClass.getName()); - EjbOptionalIntfGenerator interfaceGenerator = new EjbOptionalIntfGenerator(targetObjectClass.getClassLoader()); + EjbOptionalIntfGenerator interfaceGenerator = new EjbOptionalIntfGenerator(); interfaceGenerator.generateOptionalLocalInterface(targetObjectClass, subClassInterfaceName); - Class subClassInterface = interfaceGenerator.loadClass(subClassInterfaceName); + Class subClassInterface = interfaceGenerator.loadClass(subClassInterfaceName, targetObjectClass); String beanSubClassName = subClassInterfaceName + "__Bean__"; @@ -56,7 +56,7 @@ public JavaEEInterceptorBuilder createBuilder(InterceptorInfo info) throws Excep // InvocationHandler. interfaceGenerator.generateOptionalLocalInterfaceSubClass(targetObjectClass, beanSubClassName, subClassInterface); - Class subClass = interfaceGenerator.loadClass(beanSubClassName); + Class subClass = interfaceGenerator.loadClass(beanSubClassName, targetObjectClass); // TODO do interceptor builder once per managed bean InterceptorManager interceptorManager = diff --git a/appserver/ejb/ejb-container/src/test/java/com/sun/ejb/EJBUtilsTest.java b/appserver/ejb/ejb-container/src/test/java/com/sun/ejb/EJBUtilsTest.java index 6c95681967b..1f4f545695c 100644 --- a/appserver/ejb/ejb-container/src/test/java/com/sun/ejb/EJBUtilsTest.java +++ b/appserver/ejb/ejb-container/src/test/java/com/sun/ejb/EJBUtilsTest.java @@ -71,7 +71,7 @@ public void loadGeneratedGenericEJBHomeClass() throws Exception { Class newClass = EJBUtils.loadGeneratedGenericEJBHomeClass(loader, GeneratorTestExperiment.class); assertNotNull(newClass); assertTrue(newClass.isInterface()); - assertEquals("com.sun.ejb.codegen.GenericEJBHome_Generated", newClass.getName()); + assertEquals(GeneratorTestExperiment.class.getPackageName() + ".GenericEJBHome_Generated", newClass.getName()); assertSame(newClass, factory.ensureGenericHome(GeneratorTestExperiment.class)); } diff --git a/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanContainer.java b/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanContainer.java index 5691eee049c..7a042a733ef 100644 --- a/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanContainer.java +++ b/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanContainer.java @@ -162,7 +162,7 @@ public final class MessageBeanContainer extends BaseContainer implements Message Class messageListenerType_1 = messageListenerType_; if (isModernMessageListener(messageListenerType_1)) { // Generate interface and subclass for EJB 3.2 No-interface MDB VIew - MessageBeanInterfaceGenerator generator = new MessageBeanInterfaceGenerator(classLoader); + MessageBeanInterfaceGenerator generator = new MessageBeanInterfaceGenerator(); messageBeanInterface = generator.generateMessageBeanInterface(beanClass); messageBeanSubClass = generator.generateMessageBeanSubClass(beanClass, messageBeanInterface); } diff --git a/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanInterfaceGenerator.java b/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanInterfaceGenerator.java index dfff49d4e01..145ea05432b 100644 --- a/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanInterfaceGenerator.java +++ b/appserver/ejb/ejb-full-container/src/main/java/org/glassfish/ejb/mdb/MessageBeanInterfaceGenerator.java @@ -23,16 +23,12 @@ public class MessageBeanInterfaceGenerator extends EjbOptionalIntfGenerator { - public MessageBeanInterfaceGenerator(ClassLoader loader) { - super(loader); - } - @SuppressWarnings("unchecked") public Class generateMessageBeanSubClass(Class beanClass, Class messageBeanInterface) throws Exception { final String generatedMessageBeanSubClassName = messageBeanInterface.getName() + "__Bean__"; generateSubclass(beanClass, generatedMessageBeanSubClassName, messageBeanInterface, MessageEndpoint.class); - return (Class) loadClass(generatedMessageBeanSubClassName); + return (Class) loadClass(generatedMessageBeanSubClassName, beanClass); } public Class generateMessageBeanInterface(Class beanClass) throws Exception { @@ -40,7 +36,7 @@ public Class generateMessageBeanInterface(Class beanClass) throws Exceptio generateInterface(beanClass, generatedMessageBeanInterfaceName, MessageEndpoint.class); - return loadClass(generatedMessageBeanInterfaceName); + return loadClass(generatedMessageBeanInterfaceName, beanClass); } public static String getGeneratedMessageBeanInterfaceName(Class ejbClass) { diff --git a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java index 24449851e51..62dc619ac09 100644 --- a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java +++ b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java @@ -20,11 +20,14 @@ import com.sun.ejb.codegen.ClassGenerator; import java.security.ProtectionDomain; +import java.util.Arrays; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.internal.api.ClassLoaderHierarchy; import org.jboss.weld.serialization.spi.ProxyServices; +import static com.sun.ejb.codegen.Generator.getPackageName; + /** * An implementation of the {@link ProxyServices}. *

@@ -53,18 +56,20 @@ public ProxyServicesImpl(final ServiceLocator services) { @Override public Class defineClass(final Class originalClass, final String className, final byte[] classBytes, final int off, final int len) throws ClassFormatError { - return defineClass(originalClass, className, classBytes, off, len, null); + try { + String packageName = getPackageName(className); + byte[] classData = Arrays.copyOfRange(classBytes, off, off+len); + return ClassGenerator.defineClass(originalClass.getClassLoader(), originalClass, packageName, className, classData); + } catch (final Exception e) { + throw new WeldProxyException("Could not define class " + className, e); + } } @Override public Class defineClass(final Class originalClass, final String className, final byte[] classBytes, final int off, final int len, final ProtectionDomain protectionDomain) throws ClassFormatError { - final ClassLoader loader = getClassLoaderforBean(originalClass); - if (protectionDomain == null) { - return defineClass(loader, className, classBytes, off, len); - } - return defineClass(loader, className, classBytes, off, len, protectionDomain); + return defineClass(originalClass, className, classBytes, off, len); } @@ -126,26 +131,4 @@ private Class loadClassByThreadCL(final String className) throws ClassNotFoun return Class.forName(className, true, Thread.currentThread().getContextClassLoader()); } - - private Class defineClass( - final ClassLoader loader, final String className, - final byte[] b, final int off, final int len, - final ProtectionDomain protectionDomain) { - try { - return ClassGenerator.defineClass(loader, className, b, 0, len, protectionDomain); - } catch (final Exception e) { - throw new WeldProxyException("Could not define class " + className, e); - } - } - - - private Class defineClass( - final ClassLoader loader, final String className, - final byte[] b, final int off, final int len) { - try { - return ClassGenerator.defineClass(loader, className, b, 0, len); - } catch (final Exception e) { - throw new WeldProxyException("Could not define class " + className, e); - } - } } From daf8de2e6081a320cb80c8dcd0b08565c85bca8d Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 28 Aug 2024 04:26:11 +0200 Subject: [PATCH 2/4] JPMS improvements: use JPMS Lookup class for Weld proxies Weld specifies a different package name for proxies than for the original class. We change the name of the proxy class using Javassist to keep the original package name so that we can use the Lookup method. We keep separate Javassist class pools for separate application classloaders, so that applications don't share them. --- .../weld/services/ProxyServicesImpl.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java index 62dc619ac09..7797387ee6c 100644 --- a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java +++ b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java @@ -19,14 +19,19 @@ import com.sun.ejb.codegen.ClassGenerator; +import java.io.ByteArrayInputStream; import java.security.ProtectionDomain; -import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.internal.api.ClassLoaderHierarchy; import org.jboss.weld.serialization.spi.ProxyServices; -import static com.sun.ejb.codegen.Generator.getPackageName; +import javassist.ClassPool; +import javassist.CtClass; + /** * An implementation of the {@link ProxyServices}. @@ -45,6 +50,7 @@ public class ProxyServicesImpl implements ProxyServices { private final ClassLoaderHierarchy classLoaderHierarchy; + private Map classPoolMap = Collections.synchronizedMap(new WeakHashMap<>()); /** * @param services immediately used to find a {@link ClassLoaderHierarchy} service @@ -57,9 +63,17 @@ public ProxyServicesImpl(final ServiceLocator services) { public Class defineClass(final Class originalClass, final String className, final byte[] classBytes, final int off, final int len) throws ClassFormatError { try { - String packageName = getPackageName(className); - byte[] classData = Arrays.copyOfRange(classBytes, off, off+len); - return ClassGenerator.defineClass(originalClass.getClassLoader(), originalClass, packageName, className, classData); + final String originalPackageName = originalClass.getPackageName(); + String modifiedClassName = originalClass.getName() + "_GlassFishWeldProxy"; + final ClassLoader originalClassCL = getClassLoaderforBean(originalClass); + final ClassPool classPool = classPoolMap.computeIfAbsent(originalClassCL, cl -> new ClassPool()); + while (classPool.getOrNull(modifiedClassName) != null) { + modifiedClassName += "_"; + } + final CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classBytes, off, len)); + ctClass.setName(modifiedClassName); + return ClassGenerator.defineClass(originalClass.getClassLoader(), originalClass, originalPackageName, + modifiedClassName, ctClass.toBytecode()); } catch (final Exception e) { throw new WeldProxyException("Could not define class " + className, e); } @@ -127,8 +141,4 @@ private boolean isApplicationClassLoader(ClassLoader classLoader) { } - private Class loadClassByThreadCL(final String className) throws ClassNotFoundException { - return Class.forName(className, true, Thread.currentThread().getContextClassLoader()); - } - } From 7cd0dc3e946389efe65c2a8c298be06837c43046 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 28 Aug 2024 04:39:56 +0200 Subject: [PATCH 3/4] JPMS improvements: use JPMS Lookup class in other places --- .../java/com/sun/ejb/codegen/AsmSerializableBeanGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/AsmSerializableBeanGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/AsmSerializableBeanGenerator.java index 0e39caaa188..08dfb5e6373 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/AsmSerializableBeanGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/AsmSerializableBeanGenerator.java @@ -81,7 +81,7 @@ public Class generateSerializableSubclass() { cw.visitEnd(); PrivilegedAction> action = - () -> defineClass(loader, subClassName, cw.toByteArray(), superClass.getProtectionDomain()); + () -> defineClass(loader, superClass, getPackageName(subClassName), subClassName, cw.toByteArray()); return AccessController.doPrivileged(action); } From 3e8556316990229f141e628eaaf742a8a10bce74 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Mon, 2 Sep 2024 23:57:31 +0200 Subject: [PATCH 4/4] JPMS improvements: Experiments with GF Embedded Discovered a flaw in the solution for ProxyServiceImpl --- .../com/sun/ejb/codegen/ClassGenerator.java | 25 +------------------ .../weld/services/ProxyServicesImpl.java | 8 ++++-- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java index 6b729bd898e..2749b79faf6 100644 --- a/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java +++ b/appserver/ejb/ejb-container/src/main/java/com/sun/ejb/codegen/ClassGenerator.java @@ -44,29 +44,6 @@ public final class ClassGenerator { private static final Logger LOG = Logger.getLogger(ClassGenerator.class.getName()); -// private static Method defineClassMethod; -// private static Method defineClassMethodSM; -// -// static { -// try { -// final PrivilegedExceptionAction action = () -> { -// final Class cl = Class.forName("java.lang.ClassLoader"); -// final String name = "defineClass"; -// defineClassMethod = cl.getDeclaredMethod(name, String.class, byte[].class, int.class, int.class); -// ClassLoaderMethods.defineClassMethod.setAccessible(true); -// defineClassMethodSM = cl.getDeclaredMethod( -// name, String.class, byte[].class, int.class, int.class, ProtectionDomain.class); -// ClassLoaderMethods.defineClassMethodSM.setAccessible(true); -// return null; -// }; -// AccessController.doPrivileged(action); -// LOG.config("ClassLoader methods capable of generating classes were successfully detected."); -// } catch (final Exception e) { -// throw new Error("Could not initialize access to ClassLoader.defineClass method.", e); -// } -// } - - private ClassGenerator() { // hidden } @@ -256,7 +233,7 @@ private static boolean useMethodHandles(final ClassLoader loader, final Class } // The bootstrap CL used by embedded glassfish doesn't remember generated classes // Further ClassLoader.findClass calls will fail. - if (anchorClass == null || loader.getParent() == null || loader.getClass() == ASURLClassLoader.class) { + if (anchorClass == null || loader.getClass() == ASURLClassLoader.class) { return false; } // Use MethodHandles.Lookup only if the anchor run-time Package defined by CL. diff --git a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java index 7797387ee6c..bf172a98295 100644 --- a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java +++ b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/services/ProxyServicesImpl.java @@ -50,7 +50,8 @@ public class ProxyServicesImpl implements ProxyServices { private final ClassLoaderHierarchy classLoaderHierarchy; - private Map classPoolMap = Collections.synchronizedMap(new WeakHashMap<>()); + // probably shouldn't be static but moved to a singleton service + private static Map classPoolMap = Collections.synchronizedMap(new WeakHashMap<>()); /** * @param services immediately used to find a {@link ClassLoaderHierarchy} service @@ -59,13 +60,16 @@ public ProxyServicesImpl(final ServiceLocator services) { classLoaderHierarchy = services.getService(ClassLoaderHierarchy.class); } + /* This solution is not ideal - it creates the new class in the CL of the originalClass + (most often server CL or bootstrap CL), while the previous solution created it in the app classloader. + */ @Override public Class defineClass(final Class originalClass, final String className, final byte[] classBytes, final int off, final int len) throws ClassFormatError { try { final String originalPackageName = originalClass.getPackageName(); String modifiedClassName = originalClass.getName() + "_GlassFishWeldProxy"; - final ClassLoader originalClassCL = getClassLoaderforBean(originalClass); + final ClassLoader originalClassCL = originalClass.getClassLoader(); final ClassPool classPool = classPoolMap.computeIfAbsent(originalClassCL, cl -> new ClassPool()); while (classPool.getOrNull(modifiedClassName) != null) { modifiedClassName += "_";