[FEATURE][EXPERIMENTAL] Implement modular class transformation, fix class transform filter

This commit is contained in:
Gravita 2025-08-13 20:08:56 +07:00
parent 6ead3c336e
commit b0715e0922
2 changed files with 122 additions and 5 deletions

View file

@ -9,6 +9,7 @@ public class LaunchOptions {
public static final class ModuleConf { public static final class ModuleConf {
public boolean enableModularClassTransform = true;
public List<String> modules = new ArrayList<>(); public List<String> modules = new ArrayList<>();
public List<String> modulePath = new ArrayList<>(); public List<String> modulePath = new ArrayList<>();
public Map<String, String> exports = new HashMap<>(); public Map<String, String> exports = new HashMap<>();

View file

@ -5,21 +5,26 @@
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.module.Configuration; import java.lang.module.*;
import java.lang.module.ModuleFinder;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModuleLaunch implements Launch { public class ModuleLaunch implements Launch {
private ModuleClassLoader moduleClassLoader; private ModuleClassLoader moduleClassLoader;
@ -59,6 +64,10 @@ public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOption
if(options.moduleConf != null) { if(options.moduleConf != null) {
// Create Module Layer // Create Module Layer
moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).map(Path::toAbsolutePath).toArray(Path[]::new)); moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).map(Path::toAbsolutePath).toArray(Path[]::new));
if(options.moduleConf.enableModularClassTransform) {
AtomicReference<ModuleClassLoader> clRef = new AtomicReference<>(moduleClassLoader);
moduleFinder = new CustomModuleFinder(moduleFinder, clRef);
}
ModuleLayer bootLayer = ModuleLayer.boot(); ModuleLayer bootLayer = ModuleLayer.boot();
if(options.moduleConf.modules.contains("ALL-MODULE-PATH")) { if(options.moduleConf.modules.contains("ALL-MODULE-PATH")) {
var set = moduleFinder.findAll(); var set = moduleFinder.findAll();
@ -182,6 +191,105 @@ private static String getPackageFromClass(String clazz) {
return clazz; return clazz;
} }
private class CustomModuleFinder implements ModuleFinder {
private final ModuleFinder delegate;
private AtomicReference<ModuleClassLoader> cl;
public CustomModuleFinder(ModuleFinder delegate, AtomicReference<ModuleClassLoader> cl) {
this.delegate = delegate;
this.cl = cl;
}
@Override
public Optional<ModuleReference> find(String name) {
return delegate.find(name).map(this::makeModuleReference);
}
@Override
public Set<ModuleReference> findAll() {
return delegate.findAll().stream()
.map(this::makeModuleReference)
.collect(Collectors.toSet());
}
private CustomModuleReference makeModuleReference(ModuleReference x) {
return new CustomModuleReference(x.descriptor(), x.location().orElse(null), x, cl);
}
}
private class CustomModuleReference extends ModuleReference {
private final ModuleReference delegate;
private final AtomicReference<ModuleClassLoader> cl;
public CustomModuleReference(ModuleDescriptor descriptor, URI location, ModuleReference delegate, AtomicReference<ModuleClassLoader> cl) {
super(descriptor, location);
this.delegate = delegate;
this.cl = cl;
}
@Override
public ModuleReader open() throws IOException {
return new CustomModuleReader(delegate.open(), cl, descriptor());
}
}
private class CustomModuleReader implements ModuleReader {
private final ModuleReader delegate;
private final AtomicReference<ModuleClassLoader> cl;
private final ModuleDescriptor descriptor;
public CustomModuleReader(ModuleReader delegate, AtomicReference<ModuleClassLoader> cl, ModuleDescriptor descriptor) {
this.delegate = delegate;
this.cl = cl;
this.descriptor = descriptor;
}
@Override
public Optional<URI> find(String name) throws IOException {
return delegate.find(name);
}
@Override
public Optional<InputStream> open(String name) throws IOException {
ModuleClassLoader classLoader = cl.get();
if(classLoader == null || !name.endsWith(".class")) {
return delegate.open(name);
}
var inputOptional = delegate.open(name);
if(inputOptional.isEmpty()) {
return inputOptional;
}
try(ByteArrayOutputStream output = new ByteArrayOutputStream()) {
inputOptional.get().transferTo(output);
var realClassName = name.replace("/", ".").substring(0, name.length()-".class".length()-1);
byte[] bytes = classLoader.transformClass(descriptor.name(), realClassName, output.toByteArray());
return Optional.of(new ByteArrayInputStream(bytes));
}
}
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
// TODO class transformation unimplemented
return delegate.read(name);
}
@Override
public void release(ByteBuffer bb) {
// TODO class transformation unimplemented
delegate.release(bb);
}
@Override
public Stream<String> list() throws IOException {
return delegate.list();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
private class ModuleClassLoader extends URLClassLoader { private class ModuleClassLoader extends URLClassLoader {
private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>(); private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
@ -251,9 +359,7 @@ protected Class<?> findClass(String moduleName, String name) {
String rawClassName = name.replace(".", "/").concat(".class"); String rawClassName = name.replace(".", "/").concat(".class");
try(InputStream input = getResourceAsStream(rawClassName)) { try(InputStream input = getResourceAsStream(rawClassName)) {
byte[] bytes = IOHelper.read(input); byte[] bytes = IOHelper.read(input);
for(ClassLoaderControl.ClassTransformer t : transformers) { bytes = transformClass(moduleName, name, bytes);
bytes = t.transform(moduleName, name, null, bytes);
}
clazz = defineClass(name, bytes, 0, bytes.length); clazz = defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) { } catch (IOException e) {
return null; return null;
@ -286,6 +392,16 @@ protected Class<?> findClass(String moduleName, String name) {
} }
} }
private byte[] transformClass(String moduleName, String name, byte[] bytes) {
for(ClassLoaderControl.ClassTransformer t : transformers) {
if(!t.filter(moduleName, name)) {
continue;
}
bytes = t.transform(moduleName, name, null, bytes);
}
return bytes;
}
@Override @Override
public String findLibrary(String name) { public String findLibrary(String name) {
if(nativePath == null) { if(nativePath == null) {