Skip to content

Commit

Permalink
Use sun.misc.Unsafe to inject URLs into ClassLoaders on Java 9+ (luck…
Browse files Browse the repository at this point in the history
  • Loading branch information
Revxrsal authored Aug 13, 2021
1 parent 4fbde6c commit c580b0a
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package me.lucko.helper.sql.plugin;

import me.lucko.helper.internal.HelperImplementationPlugin;
import me.lucko.helper.maven.MavenLibrary;
import me.lucko.helper.plugin.ExtendedJavaPlugin;
import me.lucko.helper.sql.DatabaseCredentials;
import me.lucko.helper.sql.Sql;
Expand All @@ -34,7 +35,7 @@
import javax.annotation.Nonnull;

@HelperImplementationPlugin
//@MavenLibrary(groupId = "org.slf4j", artifactId = "slf4j-api", version = "1.7.30")
@MavenLibrary(groupId = "org.slf4j", artifactId = "slf4j-api", version = "1.7.30")
public class HelperSqlPlugin extends ExtendedJavaPlugin implements SqlProvider {
private DatabaseCredentials globalCredentials;
private Sql globalDataSource;
Expand Down
13 changes: 5 additions & 8 deletions helper/src/main/java/me/lucko/helper/maven/LibraryLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,18 @@
/**
* Resolves {@link MavenLibrary} annotations for a class, and loads the dependency
* into the classloader.
*
* @deprecated No longer works on Java 16+, no plans to fix.
*/
@NonnullByDefault
@Deprecated
public final class LibraryLoader {

@SuppressWarnings("Guava")
private static final Supplier<Method> ADD_URL_METHOD = Suppliers.memoize(() -> {
private static final Supplier<URLInjector> URL_INJECTOR = Suppliers.memoize(() -> {
try {
Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
return addUrlMethod;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
return addUrlMethod::invoke;
} catch (ReflectiveOperationException e) {
return UnsafeURLInjector.create((URLClassLoader) LoaderUtils.getPlugin().getClass().getClassLoader());
}
});

Expand Down Expand Up @@ -123,7 +120,7 @@ public static void load(Dependency d) {

URLClassLoader classLoader = (URLClassLoader) LoaderUtils.getPlugin().getClass().getClassLoader();
try {
ADD_URL_METHOD.get().invoke(classLoader, saveLocation.toURI().toURL());
URL_INJECTOR.get().addURL(classLoader, saveLocation.toURI().toURL());
} catch (Exception e) {
throw new RuntimeException("Unable to load dependency: " + saveLocation.toString(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface MavenLibraries {

@Nonnull
Expand Down
3 changes: 0 additions & 3 deletions helper/src/main/java/me/lucko/helper/maven/MavenLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,11 @@

/**
* Annotation to indicate a required library for a class.
*
* @deprecated No longer works on Java 16+, no plans to fix.
*/
@Documented
@Repeatable(MavenLibraries.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface MavenLibrary {

/**
Expand Down
1 change: 0 additions & 1 deletion helper/src/main/java/me/lucko/helper/maven/Repository.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
@Documented
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface Repository {

/**
Expand Down
21 changes: 21 additions & 0 deletions helper/src/main/java/me/lucko/helper/maven/URLInjector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package me.lucko.helper.maven;

import javax.annotation.Nonnull;
import java.net.URL;
import java.net.URLClassLoader;

/**
* Handles injecting URLs into a {@link URLClassLoader}
*/
interface URLInjector {

/**
* Adds the given URL to the class loader
*
* @param classLoader ClassLoader to add to
* @param url URL to add
* @throws Exception Any exception whilst adding
*/
void addURL(@Nonnull ClassLoader classLoader, @Nonnull URL url) throws Exception;

}
56 changes: 56 additions & 0 deletions helper/src/main/java/me/lucko/helper/maven/UnsafeURLInjector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package me.lucko.helper.maven;

import sun.misc.Unsafe;

import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayDeque;
import java.util.ArrayList;

/**
* An implementation of {@link URLInjector} that uses sun.misc.Unsafe to
* inject URLs directly into a {@link URLClassLoader}'s paths.
* <p>
* This implementation works on Java 9+ only.
*/
final class UnsafeURLInjector implements URLInjector {

private final ArrayDeque<URL> unopenedURLs;
private final ArrayList<URL> pathURLs;

public UnsafeURLInjector(final ArrayDeque<URL> unopenedURLs, final ArrayList<URL> pathURLs) {
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}

public static URLInjector create(final URLClassLoader classLoader) {
try {
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
final Unsafe unsafe = (Unsafe) field.get(null);
final Object ucp = fetchField(unsafe, URLClassLoader.class, classLoader, "ucp");
final ArrayDeque<URL> unopenedURLs = (ArrayDeque<URL>) fetchField(unsafe, ucp, "unopenedUrls");
final ArrayList<URL> pathURLs = (ArrayList<URL>) fetchField(unsafe, ucp, "path");
return new UnsafeURLInjector(unopenedURLs, pathURLs);
} catch (Throwable t) {
return (loader, url) -> { throw new UnsupportedOperationException(); };
}
}

private static Object fetchField(final Unsafe unsafe, final Object object, final String name) throws NoSuchFieldException {
return fetchField(unsafe, object.getClass(), object, name);
}

private static Object fetchField(final Unsafe unsafe, final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
final Field field = clazz.getDeclaredField(name);
final long offset = unsafe.objectFieldOffset(field);
return unsafe.getObject(object, offset);
}

@Override public void addURL(@Nonnull ClassLoader classLoader, @Nonnull URL url) {
unopenedURLs.addLast(url);
pathURLs.add(url);
}
}

0 comments on commit c580b0a

Please sign in to comment.