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

Support defining classes in multiple JDK versions. #45

Merged
merged 2 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.
52 changes: 52 additions & 0 deletions classloader/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?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>
<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>

<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,46 @@
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) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(DefineClassPermission.getInstance());
}

// 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,44 @@
package io.smallrye.common.classloader;

import java.security.Permission;

public class DefineClassPermission extends Permission {
private static final long serialVersionUID = 142067672163413424L;
private static final DefineClassPermission INSTANCE = new DefineClassPermission();

public DefineClassPermission() {
super("");
}

public DefineClassPermission(final String name, final String actions) {
this();
}

public static DefineClassPermission getInstance() {
return INSTANCE;
}

@Override
public boolean implies(final Permission permission) {
return permission != null && permission.getClass() == this.getClass();
}

@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
} else {
return obj != null && obj.getClass() == this.getClass();
}
}

@Override
public int hashCode() {
return getClass().hashCode();
}

@Override
public String getActions() {
return "";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(DefineClassPermission.getInstance());
}

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