Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy creation improvements for JPMS, remove the need for --add-opens in some cases #25132

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public Class<?> generateSerializableSubclass() {
cw.visitEnd();

PrivilegedAction<Class<?>> action =
() -> defineClass(loader, subClassName, cw.toByteArray(), superClass.getProtectionDomain());
() -> defineClass(loader, superClass, getPackageName(subClassName), subClassName, cw.toByteArray());

return AccessController.doPrivileged(action);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,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<Void> 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 ClassGenerator() {
// hidden
}
Expand Down Expand Up @@ -103,12 +78,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});
Expand All @@ -133,7 +113,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);
Expand All @@ -151,13 +136,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<Class<?>> 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);
}
Expand All @@ -176,7 +165,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);
Expand All @@ -195,15 +188,19 @@ 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,
final ProtectionDomain protectionDomain) throws ClassDefinitionException {
LOG.log(CONFIG, "Defining class: {0} by loader: {1}", new Object[] {className, loader});
final PrivilegedAction<Class<?>> 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);
}
Expand Down Expand Up @@ -236,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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Void> 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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,25 @@ public class EjbOptionalIntfGenerator extends BeanGeneratorBase {
private static final String DELEGATE_FIELD_NAME = "__ejb31_delegate";

private final Map<String, byte[]> 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<Class<?>> action = () -> defineClass(loader, name, classData, protectionDomain);
PrivilegedAction<Class<?>> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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__";

Expand All @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,20 @@

public class MessageBeanInterfaceGenerator extends EjbOptionalIntfGenerator {

public MessageBeanInterfaceGenerator(ClassLoader loader) {
super(loader);
}

@SuppressWarnings("unchecked")
public <T> Class<? extends T> generateMessageBeanSubClass(Class<?> beanClass, Class<T> messageBeanInterface) throws Exception {
final String generatedMessageBeanSubClassName = messageBeanInterface.getName() + "__Bean__";

generateSubclass(beanClass, generatedMessageBeanSubClassName, messageBeanInterface, MessageEndpoint.class);
return (Class<? extends T>) loadClass(generatedMessageBeanSubClassName);
return (Class<? extends T>) loadClass(generatedMessageBeanSubClassName, beanClass);
}

public Class<?> generateMessageBeanInterface(Class<?> beanClass) throws Exception {
final String generatedMessageBeanInterfaceName = getGeneratedMessageBeanInterfaceName(beanClass);

generateInterface(beanClass, generatedMessageBeanInterfaceName, MessageEndpoint.class);

return loadClass(generatedMessageBeanInterfaceName);
return loadClass(generatedMessageBeanInterfaceName, beanClass);
}

public static String getGeneratedMessageBeanInterfaceName(Class<?> ejbClass) {
Expand Down
Loading