Skip to content

Commit

Permalink
[WFLY-11406] Properly fix DefaultClassFactory so it can be used on JD…
Browse files Browse the repository at this point in the history
…K12 and above.

Some background about this change:

After bisecting openjdk code base we identified this commit
openjdk/jdk@3f0dace
which introduced new JDK behaviour WRT reflective access to some fields and methods.
Since this commit it is impossible to:

A) Reflectively access all fields and methods of:

    Reflection.class
    AccessibleObject.class
    ClassLoader.class
    Constructor.class
    Field.class
    Method.class

B) Reflectively access fields:

    Class.class -> "classLoader"
    System.class -> "security"

The proper fix is to use MethodHandles to workaround this reflection limitations.
  • Loading branch information
ropalka committed Oct 21, 2022
1 parent 92fef02 commit 4028c25
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 62 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkMode>once</forkMode>
<argLine>--add-opens=java.base/java.lang=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
Expand Down
15 changes: 2 additions & 13 deletions src/main/java/org/jboss/classfilewriter/ClassFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

/**
* @author Stuart Douglas
* @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
*/
public class ClassFile implements WritableEntry {

Expand Down Expand Up @@ -90,18 +91,7 @@ public ClassFile(String name, int accessFlags, String superclass, ClassLoader cl

@Deprecated
public ClassFile(String name, int accessFlags, String superclass, int version, ClassLoader classLoader, String... interfaces) {
if(version > JavaVersions.JAVA_6 && classLoader == null) {
throw new IllegalArgumentException("ClassLoader must be specified if version is greater than Java 6");
}
this.version = version;
this.classLoader = classLoader;
this.classFactory = null; // allowed to be null for backward compatibility reasons
this.name = name.replace('/', '.'); // store the name in . form
this.superclass = superclass;
this.accessFlags = accessFlags;
this.interfaces.addAll(Arrays.asList(interfaces));
this.runtimeVisibleAnnotationsAttribute = new AnnotationsAttribute(AnnotationsAttribute.Type.RUNTIME_VISIBLE, constPool);
this.attributes.add(runtimeVisibleAnnotationsAttribute);
this(name, accessFlags, superclass, version, classLoader, DefaultClassFactory.INSTANCE, interfaces);
}

public ClassFile(String name, String superclass, ClassLoader classLoader, ClassFactory classFactory, String... interfaces) {
Expand Down Expand Up @@ -295,7 +285,6 @@ public Class<?> define(ClassLoader loader, ProtectionDomain domain) {

private Class<?> defineInternal(ClassLoader loader, ProtectionDomain domain) {
byte[] b = toBytecode();
final ClassFactory classFactory = this.classFactory == null ? DefaultClassFactory.INSTANCE : this.classFactory;
return classFactory.defineClass(loader, name, b, 0, b.length, domain);
}

Expand Down
77 changes: 29 additions & 48 deletions src/main/java/org/jboss/classfilewriter/DefaultClassFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,61 +17,47 @@
*/
package org.jboss.classfilewriter;

import sun.misc.Unsafe;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;

/**
* Default class definition factory. This factory maintains backward compatibility
* but it doesn't work on JDK 12 and above where ClassLoader reflection magic is forbidden.
* Default class definition factory.
*
* @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
*/
final class DefaultClassFactory implements ClassFactory {

static final ClassFactory INSTANCE = new DefaultClassFactory();

private final java.lang.reflect.Method defineClass1, defineClass2;
private static final String DEFINE_CLASS_METHOD_NAME = "defineClass";
private static final MethodHandle defineClassWithoutDomainParam, defineClassWithDomainParam;

private DefaultClassFactory() {
static {
MethodHandle[] defineClassMethods;
try {
Method[] defineClassMethods = AccessController.doPrivileged(new PrivilegedExceptionAction<Method[]>() {
public Method[] run() throws Exception {
final sun.misc.Unsafe UNSAFE;
final long overrideOffset;
// first we need to grab Unsafe
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
overrideOffset = UNSAFE.objectFieldOffset(AccessibleObject.class.getDeclaredField("override"));
} catch (Exception e) {
throw new Error(e);
}
// now we gain access to CL.defineClass methods
Class<?> cl = ClassLoader.class;
Method defClass1 = cl.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class,
int.class });
Method defClass2 = cl.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class,
int.class, ProtectionDomain.class });
// use Unsafe to crack open both CL.defineClass() methods (instead of using setAccessible())
UNSAFE.putBoolean(defClass1, overrideOffset, true);
UNSAFE.putBoolean(defClass2, overrideOffset, true);
return new Method[]{defClass1, defClass2};
MethodHandles.Lookup LOOKUP = MethodHandles.privateLookupIn(ClassLoader.class, MethodHandles.lookup());
defineClassMethods = AccessController.doPrivileged(new PrivilegedExceptionAction<>() {
public MethodHandle[] run() throws Exception {
MethodHandle defineClass1 = LOOKUP.findVirtual(ClassLoader.class, DEFINE_CLASS_METHOD_NAME,
MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class));
MethodHandle defineClass2 = LOOKUP.findVirtual(ClassLoader.class, DEFINE_CLASS_METHOD_NAME,
MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class));
return new MethodHandle[]{defineClass1, defineClass2};
}
});
// set methods to volatile fields
defineClass1 = defineClassMethods[0];
defineClass2 = defineClassMethods[1];
} catch (PrivilegedActionException pae) {
throw new RuntimeException("Cannot initialize DefaultClassFactory", pae.getException());
} catch (Throwable t) {
throw new RuntimeException("Cannot initialize " + DefaultClassFactory.class.getName(), t);
}
defineClassWithoutDomainParam = defineClassMethods[0];
defineClassWithDomainParam = defineClassMethods[1];
}

static final ClassFactory INSTANCE = new DefaultClassFactory();

private DefaultClassFactory() {
// forbidden instantiation
}

@Override
Expand All @@ -91,19 +77,14 @@ public Class<?> defineClass(final ClassLoader loader, final String name,
RuntimePermission permission = new RuntimePermission("defineClassInPackage." + packageName);
sm.checkPermission(permission);
}
java.lang.reflect.Method method;
Object[] args;
if (domain == null) {
method = defineClass1;
args = new Object[]{name, b, 0, b.length};
return (Class<?>) defineClassWithoutDomainParam.invokeExact(loader, name, b, 0, b.length);
} else {
method = defineClass2;
args = new Object[]{name, b, 0, b.length, domain};
return (Class<?>) defineClassWithDomainParam.invokeExact(loader, name, b, 0, b.length, domain);
}
return (Class<?>) method.invoke(loader, args);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
Expand Down

0 comments on commit 4028c25

Please sign in to comment.