
ru.progrm_jarvis.javacommons.classloading.GcClassDefiners Maven / Gradle / Ivy
package ru.progrm_jarvis.javacommons.classloading;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.val;
import lombok.var;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.progrm_jarvis.javacommons.annotation.Internal;
import ru.progrm_jarvis.javacommons.classloading.extension.LegacyClassExtensions;
import ru.progrm_jarvis.javacommons.invoke.FullAccessLookupFactories;
import ru.progrm_jarvis.javacommons.object.Pair;
import ru.progrm_jarvis.javacommons.unsafe.UnsafeInternals;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.invoke.MethodType.methodType;
/**
* Utility for accessing {@link ClassDefiner class definers} capable od defining garbage-collected classes.
*/
@UtilityClass
public class GcClassDefiners {
private static final @NotNull ClassDefiner CLASS_DEFINER;
static {
ClassDefiner classDefiner;
try {
// by default, use stable API
classDefiner = new HiddenClassDefiner();
} catch (final Throwable x1) {
try {
// try falling back to Unsafe
classDefiner = new UnsafeClassDefiner();
} catch (final Throwable x2) {
// finally use the worst but available approach
classDefiner = new TmpClassLoaderClassDefiner();
}
}
CLASS_DEFINER = classDefiner;
}
/**
* Gets the default {@link ClassDefiner class definer}.
*
* @return the default optional {@link ClassDefiner class definer} wrapped
*/
public @NotNull ClassDefiner getDefault() {
return CLASS_DEFINER;
}
private static final class HiddenClassDefiner implements ClassDefiner {
/**
* Method handle referring to
* {@link Lookup}{@code .defineHiddenClass(byte[], boolean, Lookup.ClassOption)} method
*/
private static final MethodHandle LOOKUP__DEFINE_HIDDEN_CLASS__METHOD_HANDLE;
static {
final Class> lookupClassOptionClass;
try {
lookupClassOptionClass = Class.forName("java.lang.invoke.MethodHandles$Lookup$ClassOption");
} catch (final ClassNotFoundException e) {
throw new Error("HiddenClassDefiner is unavailable: JRE is older than 15", e);
}
val lookup = MethodHandles.publicLookup();
final MethodHandle methodHandle;
{
val methodType = methodType(
Lookup.class, byte[].class, boolean.class,
LegacyClassExtensions.arrayType(lookupClassOptionClass)
);
try {
methodHandle = lookup.findVirtual(Lookup.class, "defineHiddenClass", methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new Error("HiddenClassDefiner is unavailable: JRE is older than 15", e);
}
}
LOOKUP__DEFINE_HIDDEN_CLASS__METHOD_HANDLE = MethodHandles.insertArguments(methodHandle,
1 /* virtual method thus `0` is for `this` */ + 2 /* 3rd argument */,
uncheckedEnumValueOf(lookupClassOptionClass, "NESTMATE")
);
}
@SuppressWarnings("unchecked")
private static > @NotNull E uncheckedEnumValueOf(final @NotNull Class> type,
final @NotNull String constantName) {
return Enum.valueOf((Class) type, constantName);
}
@SneakyThrows // call to `MethodHandle#invokeExact(...)`
private static Class> defineHiddenClass(final @NotNull Lookup owner,
final byte @NotNull [] bytecode) {
return (Class>) LOOKUP__DEFINE_HIDDEN_CLASS__METHOD_HANDLE.invokeExact(owner, bytecode);
}
@Override
public Class> defineClass(
final @NonNull Lookup owner,
final @Nullable String name,
final byte @NonNull [] bytecode
) {
return defineHiddenClass(owner, bytecode);
}
@Override
public Class>[] defineClasses(
final @NonNull Lookup owner,
final byte @NotNull [] @NonNull ... bytecodes
) {
val length = bytecodes.length;
val classes = new Class>[length];
for (var i = 0; i < length; i++) classes[i] = defineHiddenClass(owner, bytecodes[i]);
return classes;
}
@Override
public List> defineClasses(
final @NonNull Lookup owner,
final @NonNull List bytecodes
) {
val classes = new ArrayList>(bytecodes.size());
for (val bytecode : bytecodes) classes.add(defineHiddenClass(owner, bytecode));
return classes;
}
@Override
@SuppressWarnings("unchecked")
public Class>[] defineClasses(
final @NonNull Lookup owner,
final @NonNull Pair<@Nullable String, byte @NotNull []>... bytecodes
) {
val length = bytecodes.length;
val classes = new Class>[length];
for (var i = 0; i < length; i++) classes[i] = defineHiddenClass(owner, bytecodes[i].getSecond());
return classes;
}
@Override
public Map> defineClasses(
final @NonNull Lookup owner,
final @NonNull Map namedBytecode
) {
val classes = new HashMap>(namedBytecode.size());
for (val entry : namedBytecode.entrySet()) classes.put(
entry.getKey(),
defineHiddenClass(owner, entry.getValue())
);
return classes;
}
}
/**
* {@link ClassDefiner class definer}
* based on {@code sun.misc.Unsafe#defineAnonymousClass(Class, byte[], Object[])}.
*/
private static final class UnsafeClassDefiner implements ClassDefiner {
/**
* Method handle referencing {@code sun.misc.Unsafe#defineAnonymousClass(Class, byte[], Object[])}.
*/
private static final @NotNull MethodHandle UNSAFE__DEFINE_ANONYMOUS_CLASS__METHOD_HANDLE;
static {
final Class> unsafeClass;
if ((unsafeClass = UnsafeInternals.UNSAFE_CLASS) == null) throw new Error("No Unsafe is available");
final Object unsafe;
{
final MethodHandle theUnsafeGetter;
{
val lookup = FullAccessLookupFactories.getDefault()
.orElseThrow(() -> new IllegalStateException("LookupFactory is unavailable"))
.create(unsafeClass);
try {
theUnsafeGetter = lookup.findStaticGetter(unsafeClass, "theUnsafe", unsafeClass);
} catch (final IllegalAccessException | NoSuchFieldException e) {
throw new Error(
'`' + unsafeClass.getName() + ".theUnsafe` field's getter cannot be found", e
);
}
}
try {
unsafe = theUnsafeGetter.invoke();
} catch (final Throwable x) {
throw new Error("Could not get value of field `" + unsafeClass.getName() + "`", x);
}
}
val methodType = methodType(Class.class, Class.class, byte[].class, Object[].class);
// LookupFactory can't be used as it's associated class-loader doesn't know about `AnonymousClassDefiner`
val lookup = MethodHandles.lookup();
final MethodHandle methodHandle;
try {
methodHandle = lookup
.findVirtual(unsafeClass, "defineAnonymousClass", methodType);
} catch (final NoSuchMethodException | IllegalAccessException e) {
throw new Error(
"Method " + unsafeClass.getName() + ".defineAnonymousClass(Class, byte[], Object[])` "
+ "cannot be found", e
);
}
UNSAFE__DEFINE_ANONYMOUS_CLASS__METHOD_HANDLE = MethodHandles.insertArguments(
methodHandle.bindTo(unsafe),
1 /* virtual method thus `0` is for `this` */ + 2 /* 3rd argument */,
(Object) null // no const-pool patches
);
}
@SneakyThrows // call to `MethodHandle#invokeExact(...)`
private static Class> defineAnonymousClass(final @NotNull Class> owner,
final byte @NotNull [] bytecode) {
return (Class>) UNSAFE__DEFINE_ANONYMOUS_CLASS__METHOD_HANDLE.invokeExact(
owner, bytecode
);
}
@Override
public Class> defineClass(
final @NonNull Lookup owner,
final @Nullable String name,
final byte @NonNull [] bytecode
) {
return defineAnonymousClass(owner.lookupClass(), bytecode);
}
@Override
public Class>[] defineClasses(
final @NonNull Lookup owner,
final byte @NotNull [] @NonNull ... bytecodes
) {
val length = bytecodes.length;
val classes = new Class>[length];
val lookupClass = owner.lookupClass();
for (var i = 0; i < length; i++) classes[i] = defineAnonymousClass(lookupClass, bytecodes[i]);
return classes;
}
@Override
public List> defineClasses(
final @NonNull Lookup owner,
final @NonNull List bytecodes
) {
val classes = new ArrayList>(bytecodes.size());
val lookupClass = owner.lookupClass();
for (val bytecode : bytecodes) classes.add(defineAnonymousClass(lookupClass, bytecode));
return classes;
}
@Override
@SuppressWarnings("unchecked")
public Class>[] defineClasses(
final @NonNull Lookup owner,
final @NonNull Pair<@Nullable String, byte @NotNull []>... bytecodes
) {
val length = bytecodes.length;
val classes = new Class>[length];
val lookupClass = owner.lookupClass();
for (var i = 0; i < length; i++) classes[i] = defineAnonymousClass(lookupClass, bytecodes[i].getSecond());
return classes;
}
@Override
public Map> defineClasses(
final @NonNull Lookup owner,
final @NonNull Map namedBytecode
) {
val classes = new HashMap>(namedBytecode.size());
val lookupClass = owner.lookupClass();
for (val entry : namedBytecode.entrySet()) classes.put(
entry.getKey(),
defineAnonymousClass(lookupClass, entry.getValue())
);
return classes;
}
}
/**
* {@link ClassDefiner Class definer} creating temporary {@link ClassLoader class-loaders} for defined classes.
*/
private static final class TmpClassLoaderClassDefiner implements ClassDefiner {
@Override
public Class> defineClass(
final @NonNull Lookup owner,
final @Nullable String name,
final byte @NonNull [] bytecode
) {
return new TmpClassLoader(owner.lookupClass().getClassLoader()).define(name, bytecode);
}
@Override
public Class>[] defineClasses(
final @NonNull Lookup owner,
final byte @NotNull [] @NonNull ... bytecodes
) {
val classLoader = new TmpClassLoader(owner.lookupClass().getClassLoader());
val length = bytecodes.length;
val classes = new Class>[length];
for (var i = 0; i < length; i++) classes[i] = classLoader.define(null, bytecodes[i]);
return classes;
}
@Override
public List> defineClasses(
final @NonNull Lookup owner,
final @NonNull List bytecodes
) {
val classLoader = new TmpClassLoader(owner.lookupClass().getClassLoader());
val classes = new ArrayList>(bytecodes.size());
for (val bytecode : bytecodes) classes.add(classLoader.define(null, bytecode));
return classes;
}
@Override
@SuppressWarnings("unchecked")
public Class>[] defineClasses(
final @NonNull Lookup owner,
final @NotNull Pair<@Nullable String, byte @NotNull []> @NonNull ... bytecodes
) {
val classLoader = new TmpClassLoader(owner.lookupClass().getClassLoader());
val length = bytecodes.length;
val classes = new Class>[length];
for (var i = 0; i < length; i++) {
val namedClass = bytecodes[i];
classes[i] = classLoader.define(namedClass.getFirst(), namedClass.getSecond());
}
return classes;
}
@Override
public Map> defineClasses(
final @NonNull Lookup owner,
final @NonNull Map<@Nullable String, byte @NotNull []> namedBytecode
) {
val classLoader = new TmpClassLoader(owner.lookupClass().getClassLoader());
val classes = new HashMap>(namedBytecode.size());
for (val entry : namedBytecode.entrySet()) {
val name = entry.getKey();
classes.put(name, classLoader.define(name, entry.getValue()));
}
return classes;
}
/**
* Temporary class-loader which should be instantiated for groups of related classes which may be unloaded.
*/
@Internal(
"This class-loader is intended only for internal usage and should never be accessed outside "
+ "as there should be no strong references to it"
)
private static final class TmpClassLoader extends ClassLoader {
/**
* Instantiates a new temporary class-loader using the given parent.
*
* @param parent parent class-loader
*/
private TmpClassLoader(final ClassLoader parent) {
super(parent);
}
/**
* Defines a class by the given bytecode.
*
* @param name canonical name of the class
* @param bytecode bytecode of the class
* @return defined class
*/
private Class> define(final @Nullable String name,
final byte @NotNull [] bytecode) {
return defineClass(name, bytecode, 0, bytecode.length);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy