Skip to content

Commit

Permalink
try with resource the downgrader
Browse files Browse the repository at this point in the history
  • Loading branch information
wagyourtail committed May 23, 2024
1 parent f33378f commit 8d4977d
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 53 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,15 @@ ex. `java -jar JvmDowngrader-all.jar bootstrap -cp myapp.jar;classpath.jar;class
This is what the bootstrap downgrader essentially uses internally.
```groovy
// add jar to default downgrading classloader
ClassDowngrader.classLoader.addDelegate(new URL[] { new File("jarname.jar").toURI().toURL() });
ClassDowngrader.getCurrentVersionDowngrader().getClassLoader().addDelegate(new URL[] { new File("jarname.jar").toURI().toURL() });
// call main method
ClassDowngrader.classLoader.loadClass("mainclass").getMethod("main", String[].class).invoke(null, new Object[] { new String[] { "args" } });
ClassDowngrader.getCurrentVersionDowngrader().getClassLoader().loadClass("mainclass").getMethod("main", String[].class).invoke(null, new Object[] { new String[] { "args" } });
```

You can also create your own downgrading classloader, for more complicated environments.
```groovy
// construct with parent of the default class downgrader classloader, as that contains the downgraded api classes.
DowngradingClassLoader loader = new DowngradingClassLoader(ClassDowngrader.classLoader);
DowngradingClassLoader loader = new DowngradingClassLoader(ClassDowngrader.getCurrentVersionDowngrader(), parent);
// adding jars
loader.addDelegate(new URL[] { new File("jarname.jar").toURI().toURL() });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ abstract class JVMDowngraderExtension(val project: Project) {
flags.classVersion = version.toOpcode()
flags.debugSkipStubs = shadeDebugSkipStubs.toSet()

ZipDowngrader.downgradeZip(ClassDowngrader.downgradeTo(flags), flags.findJavaApi(), emptySet(), downgradedPath.toPath())
ClassDowngrader.downgradeTo(flags).use {
ZipDowngrader.downgradeZip(it, flags.findJavaApi(), emptySet(), downgradedPath.toPath())
}
downgradedPath
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ abstract class DowngradeFiles : ConventionTask() {
fileSystems.add(fs)
fs.getPath("/")
} }

PathDowngrader.downgradePaths(ClassDowngrader.downgradeTo(flags), toDowngrade, downgraded, classpath.map { it.toURI().toURL() }.toSet())
ClassDowngrader.downgradeTo(flags).use {
PathDowngrader.downgradePaths(it, toDowngrade, downgraded, classpath.map { it.toURI().toURL() }.toSet())
}
} finally {
fileSystems.forEach { it.close() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,14 @@ abstract class DowngradeJar : Jar() {
flags.printDebug = debugPrint.get()
flags.classVersion = downgradeTo.toOpcode()
flags.debugSkipStubs = debugSkipStubs.get().toSet()

ZipDowngrader.downgradeZip(
ClassDowngrader.downgradeTo(flags),
inputFile.asFile.get().toPath(),
classpath.files.map { it.toURI().toURL() }.toSet(),
tempOutput.toPath()
)
ClassDowngrader.downgradeTo(flags).use {
ZipDowngrader.downgradeZip(
it,
inputFile.asFile.get().toPath(),
classpath.files.map { it.toURI().toURL() }.toSet(),
tempOutput.toPath()
)
}

inputFile.asFile.get().toPath().readZipInputStreamFor("META-INF/MANIFEST.MF", false) { inp ->
// write to temp file
Expand Down
49 changes: 33 additions & 16 deletions src/main/java/xyz/wagyourtail/jvmdg/ClassDowngrader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package xyz.wagyourtail.jvmdg;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
Expand Down Expand Up @@ -27,7 +29,7 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

public class ClassDowngrader {
public class ClassDowngrader implements Closeable {
/**
* because parent is null, this is (essentially) a wrapper around the bootstrap classloader
*/
Expand All @@ -39,33 +41,45 @@ public class ClassDowngrader {
public final Flags flags;
public final int target;

protected ClassDowngrader(Flags flags) {
protected ClassDowngrader(@NotNull Flags flags) {
this.flags = flags;
this.target = flags.classVersion;
try {
classLoader = new DowngradingClassLoader(this, new URL[]{flags.findJavaApi().toUri().toURL()}, ClassDowngrader.class.getClassLoader());
classLoader = new DowngradingClassLoader(this, ClassDowngrader.class.getClassLoader());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
downgraders = collectProviders();
}

@NotNull
@Contract("_ -> new")
public static ClassDowngrader downgradeTo(int version) {
Flags flags = new Flags();
flags.classVersion = version;
return new ClassDowngrader(flags);
}

public static ClassDowngrader downgradeTo(Flags flags) {
@NotNull
@Contract("_ -> new")
public static ClassDowngrader downgradeTo(@NotNull Flags flags) {
return new ClassDowngrader(flags.copy());
}

private static ClassDowngrader currentVersionDowngrader = null;

@NotNull
public static ClassDowngrader getCurrentVersionDowngrader() {
Flags flags = new Flags();
flags.classVersion = Utils.getCurrentClassVersion();
return new ClassDowngrader(flags);
if (currentVersionDowngrader == null) {
Flags flags = new Flags();
flags.classVersion = Utils.getCurrentClassVersion();
currentVersionDowngrader = new ClassDowngrader(flags);
}
return currentVersionDowngrader;
}

@NotNull
@Contract("_ -> new")
public static ClassDowngrader getCurrentVersionDowngrader(Flags flags) {
flags = flags.copy();
flags.classVersion = Utils.getCurrentClassVersion();
Expand All @@ -79,9 +93,7 @@ public DowngradingClassLoader getClassLoader() {
public synchronized Map<Integer, VersionProvider> collectProviders() {
Map<Integer, VersionProvider> downgraders = new HashMap<>();
try {
Iterator<VersionProvider> providerIterator = ServiceLoader.load(VersionProvider.class, classLoader).iterator();
while (providerIterator.hasNext()) {
VersionProvider provider = providerIterator.next();
for (VersionProvider provider : ServiceLoader.load(VersionProvider.class, classLoader)) {
downgraders.put(provider.inputVersion, provider);
}
} catch (Throwable t) {
Expand All @@ -90,7 +102,7 @@ public synchronized Map<Integer, VersionProvider> collectProviders() {
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load downgraders", e);
}
t.printStackTrace();
t.printStackTrace(System.err);
throw t;
}
return downgraders;
Expand Down Expand Up @@ -212,7 +224,7 @@ public Type stubClass(int version, Type type) {
return type;
}

protected Set<ClassNode> downgrade(ClassNode clazz, boolean enableRuntime, Function<String, ClassNode> getReadOnly) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
protected Set<ClassNode> downgrade(ClassNode clazz, boolean enableRuntime, Function<String, ClassNode> getReadOnly) throws IOException {
Set<ClassNode> classes = new HashSet<>();
classes.add(clazz);
int version = clazz.version;
Expand Down Expand Up @@ -248,7 +260,7 @@ public List<VersionProvider> versionProviders(int inputVersion) {
return providers;
}

public Map<String, byte[]> downgrade(/* in out */ AtomicReference<String> name, byte[] bytes, boolean enableRuntime, final Function<String, byte[]> getExtraRead) throws IllegalClassFormatException {
public Map<String, byte[]> downgrade(/* in out */ AtomicReference<String> name, @NotNull byte[] bytes, boolean enableRuntime, final Function<String, byte[]> getExtraRead) throws IllegalClassFormatException {
// check magic
if (bytes[0] != (byte) 0xCA || bytes[1] != (byte) 0xFE || bytes[2] != (byte) 0xBA ||
bytes[3] != (byte) 0xBE) {
Expand Down Expand Up @@ -296,7 +308,7 @@ public ClassNode apply(String s) {
} catch (IOException ignored) {
}
}
outputs.put(c.name, classNodeToBytes(c, getExtraRead));
outputs.put(c.name, classNodeToBytes(c));
}
} catch (Exception e) {
throw new RuntimeException("Failed to downgrade " + name.get(), e);
Expand All @@ -314,13 +326,13 @@ public ClassNode apply(String s) {
return outputs;
}

public byte[] classNodeToBytes(final ClassNode node, final Function<String, byte[]> getExtraRead) {
public byte[] classNodeToBytes(@NotNull final ClassNode node) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
node.accept(cw);
return cw.toByteArray();
}

public void writeBytesToDebug(String name, byte[] bytes) {
public void writeBytesToDebug(@NotNull String name, byte[] bytes) {
File f = new File(Constants.DEBUG_DIR, name.replace('.', '/') + ".class");
f.getParentFile().mkdirs();
try (FileOutputStream fos = new FileOutputStream(f)) {
Expand All @@ -342,4 +354,9 @@ public VersionProvider getVersionProviderFor(int version) {
return vp;
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,47 @@
import xyz.wagyourtail.jvmdg.util.Function;
import xyz.wagyourtail.jvmdg.util.Utils;

import java.io.Closeable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

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

public DowngradingClassLoader(ClassDowngrader downgrader) {
public DowngradingClassLoader(ClassDowngrader downgrader) throws MalformedURLException {
super();
delegates.add(new URLClassLoader(new URL[]{downgrader.flags.findJavaApi().toUri().toURL()}));
this.holder = downgrader;
if (downgrader.target != Utils.getCurrentClassVersion()) {
this.currentVersionDowngrader = ClassDowngrader.getCurrentVersionDowngrader(downgrader.flags);
} else {
this.currentVersionDowngrader = downgrader;
}
}

public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) {
public DowngradingClassLoader(ClassDowngrader downgrader, ClassLoader parent) throws MalformedURLException {
super(parent);
delegates.add(new URLClassLoader(new URL[]{downgrader.flags.findJavaApi().toUri().toURL()}));
this.holder = downgrader;
if (downgrader.target != Utils.getCurrentClassVersion()) {
this.currentVersionDowngrader = ClassDowngrader.getCurrentVersionDowngrader(downgrader.flags);
} else {
this.currentVersionDowngrader = downgrader;
}
}

public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls, ClassLoader parent) {
public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls, ClassLoader parent) throws MalformedURLException {
this(downgrader, parent);
delegates.add(new URLClassLoader(urls, getParent()));
}

public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls) {
public DowngradingClassLoader(ClassDowngrader downgrader, URL[] urls) throws MalformedURLException {
this(downgrader);
delegates.add(new URLClassLoader(urls, getParent()));
}
Expand Down Expand Up @@ -133,4 +140,15 @@ protected Enumeration<URL> findResources(String name) throws IOException {
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();
}
}
}
}
24 changes: 14 additions & 10 deletions src/main/java/xyz/wagyourtail/jvmdg/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ public static void debug(Map<String, List<String[]>> args) throws IOException {
if (args.get("downgradeApi").size() > 1) {
throw new IllegalArgumentException("Multiple output paths specified");
}
ZipDowngrader.downgradeZip(ClassDowngrader.downgradeTo(flags), flags.api.toPath(), new HashSet<URL>(), new File(args.get("downgradeApi").get(0)[0]).toPath());
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(flags)) {
ZipDowngrader.downgradeZip(downgrader, flags.api.toPath(), new HashSet<URL>(), new File(args.get("downgradeApi").get(0)[0]).toPath());
}
}
}

Expand Down Expand Up @@ -203,7 +205,9 @@ public static void downgrade(Map<String, List<String[]>> args) throws IOExceptio
outputs.add(entry.getValue());
}

PathDowngrader.downgradePaths(ClassDowngrader.downgradeTo(flags), inputs, outputs, getClasspath(args));
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(flags)) {
PathDowngrader.downgradePaths(downgrader, inputs, outputs, getClasspath(args));
}
} finally {
for (FileSystem fileSystem : fileSystems) {
fileSystem.close();
Expand Down Expand Up @@ -250,7 +254,7 @@ public static void shade(Map<String, List<String[]>> args) throws IOException {
}
}

public static void bootstrap(Map<String, List<String[]>> args, List<String> unparsed) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
public static void bootstrap(Map<String, List<String[]>> args, List<String> unparsed) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (!args.containsKey("--main")) {
throw new IllegalArgumentException("No main class specified");
}
Expand All @@ -261,13 +265,13 @@ public static void bootstrap(Map<String, List<String[]>> args, List<String> unpa

Set<URL> classpath = getClasspath(args);

ClassDowngrader currentVersionDowngrader = ClassDowngrader.getCurrentVersionDowngrader(flags);

currentVersionDowngrader.getClassLoader().addDelegate(classpath.toArray(new URL[0]));
Class.forName(main, false, currentVersionDowngrader.getClassLoader()).getMethod("main", String[].class).invoke(
null,
(Object) unparsed.toArray(new String[0])
);
try (ClassDowngrader currentVersionDowngrader = ClassDowngrader.getCurrentVersionDowngrader(flags)) {
currentVersionDowngrader.getClassLoader().addDelegate(classpath.toArray(new URL[0]));
Class.forName(main, false, currentVersionDowngrader.getClassLoader()).getMethod("main", String[].class).invoke(
null,
(Object) unparsed.toArray(new String[0])
);
}
}

}
4 changes: 3 additions & 1 deletion src/main/java/xyz/wagyourtail/jvmdg/compile/ApiShader.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public static void main(String[] args) throws IOException {
}

public static void downgradedApi(Flags flags, Path api, Path targetPath) throws IOException {
ZipDowngrader.downgradeZip(ClassDowngrader.downgradeTo(flags), api, new HashSet<URL>(), targetPath);
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(flags)) {
ZipDowngrader.downgradeZip(downgrader, api, new HashSet<URL>(), targetPath);
}
}

public static void shadeApis(Flags flags, String prefix, File input, File output, File downgradedApi) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public static void downgradePaths(int opcVersion, List<Path> inputRoots, List<Pa
for (File file : classpath) {
classpathPaths.add(file.toURI().toURL());
}
downgradePaths(ClassDowngrader.downgradeTo(opcVersion), inputRoots, outputRoots, classpathPaths);
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(opcVersion)) {
downgradePaths(downgrader, inputRoots, outputRoots, classpathPaths);
}
}

public static void downgradePaths(final ClassDowngrader downgrader, final List<Path> inputRoots, List<Path> outputRoots, Set<URL> classpath) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ public static void downgradeZip(int opcVersion, File input, Set<File> classpath,
for (File file : classpath) {
classpathPaths.add(file.toURI().toURL());
}
downgradeZip(ClassDowngrader.downgradeTo(opcVersion), input.toPath(), classpathPaths, output.toPath());
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(opcVersion)) {
downgradeZip(downgrader, input.toPath(), classpathPaths, output.toPath());
}
}

public static void downgradeZip(int opcVersion, Path input, Set<URL> classpath, Path output) throws IOException {
downgradeZip(ClassDowngrader.downgradeTo(opcVersion), input, classpath, output);
try (ClassDowngrader downgrader = ClassDowngrader.downgradeTo(opcVersion)) {
downgradeZip(downgrader, input, classpath, output);
}
}

public static void downgradeZip(final ClassDowngrader downgrader, Path zip, Set<URL> classpath, final Path output) throws IOException {
Expand Down
Loading

0 comments on commit 8d4977d

Please sign in to comment.