Skip to content

Commit

Permalink
Support defining classes in multiple JDK versions.
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Jul 24, 2020
1 parent 5783619 commit 19b9f1b
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 1 deletion.
Empty file.
Empty file added classloader/build-release-8
Empty file.
Empty file added classloader/build-test-java8
Empty file.
Empty file added classloader/build-test-java9
Empty file.
76 changes: 76 additions & 0 deletions classloader/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-parent</artifactId>
<version>1.1.1-SNAPSHOT</version>
</parent>

<artifactId>smallrye-common-classloader</artifactId>
<name>SmallRye Common: Classloader</name>

<dependencies>
<!-- TODO - Temporary until fix in Parent -->
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jdk-misc</artifactId>
<version>${version.jdk-misc}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Skip default Surefire runner. Tests will run with JDK profiles -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<configuration>
<skipTests>true</skipTests>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>coverage</id>
<properties>
<argLine>@{jacocoArgLine}</argLine>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.smallrye.common.classloader;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;

import sun.misc.Unsafe;

public class ClassDefiner {
private static final Unsafe unsafe;

static {
unsafe = AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
public Unsafe run() {
try {
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (NoSuchFieldException e) {
throw new NoSuchFieldError(e.getMessage());
}
}
});
}

public static Class<?> defineClass(MethodHandles.Lookup lookup, Class<?> parent, String className, byte[] classBytes) {
// The className for Unsafe.defineClass must match the one in classBytes, so we can try to verify the package
// with the parent
String parentPkg = parent.getPackage().getName();
String classPkg = className.substring(0, className.lastIndexOf('.'));

if (!parentPkg.equals(classPkg)) {
throw new IllegalArgumentException("Class not in same package as lookup class");
}

return unsafe.defineClass(className, classBytes, 0, classBytes.length, parent.getClassLoader(), null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.smallrye.common.classloader;

import java.lang.invoke.MethodHandles;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class ClassDefiner {
public static Class<?> defineClass(MethodHandles.Lookup lookup, Class<?> parent, String className, byte[] classBytes) {
return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
@Override
public Class<?> run() {
try {
MethodHandles.Lookup privateLookupIn = MethodHandles.privateLookupIn(parent, lookup);
return privateLookupIn.defineClass(classBytes);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.smallrye.common.classloader;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

class ClassDefinerTest {
@Test
void defineClass() throws Exception {
Class<?> helloClass = ClassDefiner.defineClass(MethodHandles.lookup(), ClassDefinerTest.class,
"io.smallrye.common.classloader.TestHello",
getHelloClass("io.smallrye.common.classloader.TestHello"));
assertNotNull(helloClass);

Object hello = helloClass.getDeclaredConstructor().newInstance();
Method helloMethod = helloClass.getDeclaredMethod("hello");
assertEquals("hello", helloMethod.invoke(hello));
}

@Test
void notAllowDifferentPackages() {
assertThrows(IllegalArgumentException.class,
() -> ClassDefiner.defineClass(MethodHandles.lookup(), ClassDefinerTest.class,
"io.smallrye.common.something.TestHello",
getHelloClass("io.smallrye.common.something.TestHello")));
}

private byte[] getHelloClass(final String name) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null);

{
MethodVisitor methodVisitor = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}

{
MethodVisitor methodVisitor = writer.visitMethod(ACC_PUBLIC, "hello", "()Ljava/lang/String;", null, null);
methodVisitor.visitLdcInsn("hello");
methodVisitor.visitInsn(ARETURN);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
writer.visitEnd();
}

return writer.toByteArray();
}
}
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

<modules>
<module>annotation</module>
<module>classloader</module>
<module>constraint</module>
<module>cpu</module>
<module>expression</module>
Expand Down Expand Up @@ -78,7 +79,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.0</version>
<scope>test</scope>
</dependency>

<!-- Internal module dependencies -->
<dependency>
Expand Down

0 comments on commit 19b9f1b

Please sign in to comment.