Skip to content

Instantly share code, notes, and snippets.

@gudenau
Created August 31, 2023 02:54
Show Gist options
  • Save gudenau/83f095509ddd49b0f81b01d269e3a141 to your computer and use it in GitHub Desktop.
Save gudenau/83f095509ddd49b0f81b01d269e3a141 to your computer and use it in GitHub Desktop.
final class Reprobate {
private Reprobate() {
throw new AssertionError();
}
private static final Unsafe UNSAFE = findUnsafe();
private static final MethodHandles.Lookup IMPL_LIKE = createLookup(findOverride());
private static final Object INTERNAL_UNSAFE = findInternalUnsafe();
private static final MethodHandle copyMemory0;
static {
try {
copyMemory0 = IMPL_LIKE.bind(INTERNAL_UNSAFE, "copyMemory0", MethodType.methodType(void.class, Object.class, long.class, Object.class, long.class, long.class));
} catch (Throwable e) {
throw new RuntimeException("Failed to find method(s) from the internal Unsafe", e);
}
}
@NotNull
private static Unsafe findUnsafe() {
var exceptions = new ArrayList<Throwable>();
for (var field : Unsafe.class.getDeclaredFields()) {
if(field.getType() == Unsafe.class && Modifier.isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
if(field.get(null) instanceof Unsafe unsafe) {
return unsafe;
}
} catch (Throwable e) {
exceptions.add(e);
}
}
}
var exception = new AssertionError("No unsafe, can not continue.");
exceptions.forEach(exception::addSuppressed);
throw exception;
}
@SuppressWarnings("unchecked")
private static <T> T allocateInstance(Class<T> type) {
try {
return (T) UNSAFE.allocateInstance(type);
} catch (InstantiationException e) {
throw new RuntimeException("Java is being mean to our hacks", e);
}
}
private static long findOverride() {
var object = allocateInstance(AccessibleObject.class);
for(long offset = 4; offset < 16; offset++) {
object.setAccessible(false);
if(UNSAFE.getBoolean(object, offset)) {
continue;
}
object.setAccessible(true);
if(UNSAFE.getBoolean(object, offset)) {
return offset;
}
}
throw new RuntimeException("Failed to find override");
}
private static MethodHandles.Lookup createLookup(long override) {
try {
var constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Class.class, int.class);
UNSAFE.putBoolean(constructor, override, true);
return constructor.newInstance(Object.class, null, -1);
} catch (Throwable e) {
throw new RuntimeException("Failed to create lookup", e);
}
}
private static Object findInternalUnsafe() {
try {
var type = Class.forName("jdk.internal.misc.Unsafe");
var unsafe = Stream.of(type.getDeclaredFields())
.filter((field) -> field.getType() == type && Modifier.isStatic(field.getModifiers()))
.findFirst()
.orElseThrow();
return IMPL_LIKE.unreflectGetter(unsafe).invoke();
} catch (Throwable e) {
throw new RuntimeException("The JVM is being mean", e);
}
}
private record CopyInfo(
long offset,
long size
) {}
private static final Map<Class<?>, CopyInfo> COPY_INFO = new Object2ObjectOpenHashMap<>();
@NotNull
private static CopyInfo copyInfo(Class<?> type) {
synchronized (COPY_INFO) {
return COPY_INFO.computeIfAbsent(type, Reprobate::createCopyInfo);
}
}
@NotNull
public static <T> T copy(@NotNull T value) {
try {
@SuppressWarnings("unchecked")
var type = (Class<T>) value.getClass();
var info = copyInfo(type);
var copy = allocateInstance(type);
copyMemory(value, info.offset(), copy, info.offset(), info.size());
return copy;
} catch (Throwable e) {
throw new AssertionError("Oh no, how could this happen to me?", e);
}
}
private static void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) {
try {
copyMemory0.invokeExact(srcBase, srcOffset, destBase, destOffset, bytes);
} catch (Throwable e) {
throw new RuntimeException("Failed to copy memory", e);
}
}
@NotNull
private static CopyInfo createCopyInfo(Class<?> type) {
record FieldInfo(Field field, long offset) {
private FieldInfo(Field field) {
this(field, UNSAFE.objectFieldOffset(field));
}
}
var offsets = Stream.of(type.getDeclaredFields())
.filter((field) -> !Modifier.isStatic(field.getModifiers()))
.map(FieldInfo::new)
.toArray(FieldInfo[]::new);
var min = Stream.of(offsets).min(Comparator.comparingLong(a -> a.offset)).orElseThrow();
var max = Stream.of(offsets).max(Comparator.comparingLong(a -> a.offset)).orElseThrow();
int maxSize = 4;
if(max.field.getType() == long.class || max.field.getType() == double.class) {
maxSize = 8;
}
return new CopyInfo(min.offset, max.offset + maxSize);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment