Skip to content

Commit

Permalink
rewrite downgrading classloader to not use a URLClassLoader
Browse files Browse the repository at this point in the history
  • Loading branch information
wagyourtail committed May 24, 2024
1 parent aa29707 commit 8d188fa
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ abstract class DowngradeFiles : ConventionTask() {

val downgraded = toDowngrade.map { temporaryDir.resolve(it.name) }.map {
if (it.extension == "jar" || it.extension == "zip") {
val fs = Utils.openZipFileSystem(it.toPath(), mapOf("create" to "true"))
val fs = Utils.openZipFileSystem(it.toPath(), true)
fileSystems.add(fs)
fs.getPath("/")
} else it.toPath()
}

toDowngrade = toDowngrade.map {
if (it.isDirectory()) it else run {
val fs = Utils.openZipFileSystem(it, mapOf())
val fs = Utils.openZipFileSystem(it, false)
fileSystems.add(fs)
fs.getPath("/")
}
Expand Down
6 changes: 6 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
kotlin.code.style=official
org.gradle.jvmargs=-Xmx4G
org.gradle.parallel=true

version=0.5.0

asm_version=9.7

mainVersion=7
testVersion=21

# because java 7 support is in beta*
testTargetVersion=8

stubFromVersion=8
stubToVersion=22

maven_group=xyz.wagyourtail.jvmdowngrader
archives_base_name=jvmdowngrader
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static void main(String[] args) throws IOException, URISyntaxException {

Path home = Paths.get(sym.toURI());

try (var fs = Utils.openZipFileSystem(home, new HashMap<>())) {
try (var fs = Utils.openZipFileSystem(home, false)) {
System.out.println("Successfully opened \"" + home + "\" for coverage.");

var versions = new HashMap<Integer, List<Path>>();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected ClassDowngrader(@NotNull Flags flags) {
this.target = flags.classVersion;
try {
classLoader = new DowngradingClassLoader(this, ClassDowngrader.class.getClassLoader());
} catch (MalformedURLException e) {
} catch (IOException e) {
throw new RuntimeException(e);
}
downgraders = collectProviders();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package xyz.wagyourtail.jvmdg.classloader;

import xyz.wagyourtail.jvmdg.ClassDowngrader;
import xyz.wagyourtail.jvmdg.classloader.providers.ClassLoaderResourceProvider;
import xyz.wagyourtail.jvmdg.classloader.providers.FileSystemResourceProvider;
import xyz.wagyourtail.jvmdg.util.Function;
import xyz.wagyourtail.jvmdg.util.Utils;

Expand All @@ -9,20 +11,21 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

public class DowngradingClassLoader extends ClassLoader implements Closeable {
private final ClassDowngrader holder;
private final ClassDowngrader currentVersionDowngrader;
private final List<ClassLoader> delegates = new ArrayList<>();
private final List<ResourceProvider> delegates = new ArrayList<>();

public DowngradingClassLoader(ClassDowngrader downgrader) throws MalformedURLException {
public DowngradingClassLoader(ClassDowngrader downgrader) throws IOException {
super();
Path apiJar = downgrader.flags.findJavaApi();
if (apiJar != null) {
delegates.add(new URLClassLoader(new URL[]{apiJar.toUri().toURL()}));
delegates.add(new FileSystemResourceProvider(Utils.openZipFileSystem(apiJar, false)));
}
this.holder = downgrader;
if (downgrader.target != Utils.getCurrentClassVersion()) {
Expand All @@ -32,11 +35,11 @@ public DowngradingClassLoader(ClassDowngrader downgrader) throws MalformedURLExc
}
}

public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) throws MalformedURLException {
public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) throws IOException {
super(parent);
Path apiJar = downgrader.flags.findJavaApi();
if (apiJar != null) {
delegates.add(new URLClassLoader(new URL[]{apiJar.toUri().toURL()}));
delegates.add(new FileSystemResourceProvider(Utils.openZipFileSystem(apiJar, false)));
}
this.holder = downgrader;
if (downgrader.target != Utils.getCurrentClassVersion()) {
Expand All @@ -46,22 +49,26 @@ public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) th
}
}

public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls, ClassLoader parent) throws MalformedURLException {
public DowngradingClassLoader(ClassDowngrader downgrader, List<ResourceProvider> providers, ClassLoader parent) throws IOException {
this(downgrader, parent);
delegates.add(new URLClassLoader(urls, getParent()));
delegates.addAll(providers);
}

public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls) throws MalformedURLException {
public DowngradingClassLoader(ClassDowngrader downgrader, List<ResourceProvider> providers) throws IOException {
this(downgrader);
delegates.add(new URLClassLoader(urls, getParent()));
delegates.addAll(providers);
}

public void addDelegate(ClassLoader loader) {
delegates.add(loader);
delegates.add(new ClassLoaderResourceProvider(loader));
}

public void addDelegate(FileSystem jarFile) {
delegates.add(new FileSystemResourceProvider(jarFile));
}

public void addDelegate(URL[] urls) {
delegates.add(new URLClassLoader(urls, getParent()));
delegates.add(new ClassLoaderResourceProvider(new URLClassLoader(urls)));
}

@Override
Expand Down Expand Up @@ -126,36 +133,30 @@ public byte[] apply(String s) {

@Override
protected URL findResource(String name) {
for (ClassLoader delegate : delegates) {
URL resource = delegate.getResource(name);
if (resource != null) {
return resource;
}
}
return null;
return findResources(name).nextElement();
}

@Override
protected Enumeration<URL> findResources(String name) throws IOException {
Vector<URL> vector = new Vector<>();
for (ClassLoader delegate : delegates) {
Enumeration<URL> enumeration = delegate.getResources(name);
while (enumeration.hasMoreElements()) {
vector.add(enumeration.nextElement());
protected Enumeration<URL> findResources(final String name) {
return new FlatEnumeration<>(Collections.enumeration(delegates), new Function<ResourceProvider, Enumeration<URL>>() {
@Override
public Enumeration<URL> apply(ResourceProvider resourceProvider) {
try {
return resourceProvider.getResources(name);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return vector.elements();
});
}

@Override
public void close() throws IOException {
if (holder != currentVersionDowngrader) {
currentVersionDowngrader.close();
}
for (ClassLoader delegate : delegates) {
if (delegate instanceof Closeable) {
((Closeable) delegate).close();
}
for (ResourceProvider delegate : delegates) {
delegate.close();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package xyz.wagyourtail.jvmdg.classloader;

import xyz.wagyourtail.jvmdg.util.Function;

import java.util.Enumeration;

public class FlatEnumeration<T, E> implements Enumeration<E> {
private final Enumeration<T> enumeration;
private final Function<T, Enumeration<E>> mapper;

private Enumeration<E> currentEnumeration = null;

public FlatEnumeration(Enumeration<T> enumeration, Function<T, Enumeration<E>> mapper) {
this.enumeration = enumeration;
this.mapper = mapper;
}

@Override
public boolean hasMoreElements() {
while (currentEnumeration == null || !currentEnumeration.hasMoreElements()) {
if (enumeration.hasMoreElements()) {
currentEnumeration = mapper.apply(enumeration.nextElement());
} else {
return false;
}
}
return true;
}

@Override
public E nextElement() {
if (!hasMoreElements()) {
return null;
}
return currentEnumeration.nextElement();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package xyz.wagyourtail.jvmdg.classloader;

import java.io.Closeable;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;

public interface ResourceProvider extends Closeable {

Enumeration<URL> getResources(String name) throws IOException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package xyz.wagyourtail.jvmdg.classloader.providers;

import xyz.wagyourtail.jvmdg.classloader.ResourceProvider;

import java.io.Closeable;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;

public class ClassLoaderResourceProvider implements ResourceProvider {
private final ClassLoader classLoader;

public ClassLoaderResourceProvider(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
public Enumeration<URL> getResources(String name) throws IOException {
return classLoader.getResources(name);
}

@Override
public void close() throws IOException {
if (classLoader instanceof Closeable) {
((Closeable) classLoader).close();
} else if (classLoader instanceof AutoCloseable) {
try {
((AutoCloseable) classLoader).close();
} catch (Exception e) {
throw new IOException(e);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package xyz.wagyourtail.jvmdg.classloader.providers;

import xyz.wagyourtail.jvmdg.classloader.ResourceProvider;

import java.io.IOException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

public class FileSystemResourceProvider implements ResourceProvider {
private final FileSystem jarFile;

public FileSystemResourceProvider(FileSystem jarFile) {
this.jarFile = jarFile;
}


@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> urls = new ArrayList<>();
for (Path rootDirectory : jarFile.getRootDirectories()) {
Path resource = rootDirectory.resolve(name);
if (Files.exists(resource)) {
urls.add(resource.toUri().toURL());
}
}
return Collections.enumeration(urls);
}

@Override
public void close() throws IOException {
jarFile.close();
}

}
4 changes: 2 additions & 2 deletions src/main/java/xyz/wagyourtail/jvmdg/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,13 @@ public static void getTargets(Map<String, List<String[]>> args, Map<Path, Path>
if (input.isDirectory()) {
inputPath = input.toPath();
} else {
FileSystem fs = Utils.openZipFileSystem(input.toPath(), Collections.<String, Object>emptyMap());
FileSystem fs = Utils.openZipFileSystem(input.toPath(), false);
fileSystems.add(fs);
inputPath = fs.getPath("/");
}
File output = new File(target[1]);
if (output.toString().endsWith(".jar") || output.toString().endsWith(".zip")) {
FileSystem fs = Utils.openZipFileSystem(output.toPath(), Collections.<String, Object>singletonMap("create", "true"));
FileSystem fs = Utils.openZipFileSystem(output.toPath(), true);
fileSystems.add(fs);
targets.put(inputPath, fs.getPath("/"));
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/xyz/wagyourtail/jvmdg/compile/ApiShader.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ public static void shadeApis(Flags flags, String prefix, File input, File output
if (!prefix.endsWith("/")) {
prefix += "/";
}
try (FileSystem apiFs = Utils.openZipFileSystem(resolveDowngradedApi(flags, downgradedApi), Collections.<String, Object>emptyMap())) {
try (FileSystem inputFs = Utils.openZipFileSystem(input.toPath(), Collections.<String, Object>emptyMap())) {
try (FileSystem outputFs = Utils.openZipFileSystem(output.toPath(), Collections.<String, Object>singletonMap("create", "true"))) {
try (FileSystem apiFs = Utils.openZipFileSystem(resolveDowngradedApi(flags, downgradedApi), false)) {
try (FileSystem inputFs = Utils.openZipFileSystem(input.toPath(), false)) {
try (FileSystem outputFs = Utils.openZipFileSystem(output.toPath(), true)) {
Path apiRoot = apiFs.getPath("/");
Pair<ReferenceGraph, Set<Type>> api = scanApis(apiRoot);
shadeApis(prefix, inputFs.getPath("/"), outputFs.getPath("/"), apiRoot, api.getFirst(), api.getSecond());
Expand All @@ -75,7 +75,7 @@ public static void shadeApis(Flags flags, String prefix, List<Path> inputRoots,
prefix += "/";
}
Path downgradedApiPath = resolveDowngradedApi(flags, downgradedApi);
try (FileSystem apiFs = Utils.openZipFileSystem(downgradedApiPath, Collections.<String, Object>emptyMap())) {
try (FileSystem apiFs = Utils.openZipFileSystem(downgradedApiPath,false)) {
Pair<ReferenceGraph, Set<Type>> api = scanApis(apiFs.getPath("/"));
for (int i = 0; i < inputRoots.size(); i++) {
shadeApis(prefix, inputRoots.get(i), outputRoots.get(i), apiFs.getPath("/"), api.getFirst(), api.getSecond());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ public static void downgradeZip(int opcVersion, Path input, Set<URL> classpath,
}

public static void downgradeZip(final ClassDowngrader downgrader, Path zip, Set<URL> classpath, final Path output) throws IOException {
try (final FileSystem zipfs = Utils.openZipFileSystem(zip, new HashMap<String, Object>())) {
try (final FileSystem zipfs = Utils.openZipFileSystem(zip, false)) {
Files.createDirectories(output.getParent());
Files.deleteIfExists(output);
Map<String, Object> map = new HashMap<>();
map.put("create", "true");
try (final FileSystem outputZipFs = Utils.openZipFileSystem(output, map)) {
try (final FileSystem outputZipFs = Utils.openZipFileSystem(output, true)) {
PathDowngrader.downgradePaths(downgrader, Collections.singletonList(zipfs.getPath("/")), Collections.singletonList(outputZipFs.getPath("/")), classpath);
}
}
Expand Down
13 changes: 6 additions & 7 deletions src/shared/java/xyz/wagyourtail/jvmdg/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.zip.ZipOutputStream;

public class Utils {

Expand Down Expand Up @@ -41,13 +42,11 @@ public static MethodHandles.Lookup getImplLookup() {
throw new UnsupportedOperationException("Unable to get MethodHandles.Lookup.IMPL_LOOKUP");
}

public static FileSystem openZipFileSystem(Path path, Map<String, Object> options) throws IOException {
if (options.containsKey("create")) {
if (options.get("create") == Boolean.TRUE) {
options.put("create", "true");
}
public static FileSystem openZipFileSystem(Path path, boolean create) throws IOException {
if (create && !Files.exists(path)) {
new ZipOutputStream(Files.newOutputStream(path)).close();
}
return FileSystems.newFileSystem(URI.create("jar:" + path.toUri()), options, null);
return FileSystems.newFileSystem(path, null);
}

public static byte[] readAllBytes(InputStream in) throws IOException {
Expand Down
Loading

0 comments on commit 8d188fa

Please sign in to comment.