Skip to content

Commit

Permalink
Compatibility fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ME1312 committed Mar 27, 2022
1 parent d73e96c commit 9e66b8c
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 258 deletions.
314 changes: 108 additions & 206 deletions src/net/ME1312/CBS/ASM/PlayerVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,40 @@

import net.ME1312.CBS.EmulatedPlayer;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.objectweb.asm.*;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.logging.Logger;

import static org.objectweb.asm.Opcodes.*;

public final class PlayerVisitor extends ClassVisitor {
public final class PlayerVisitor extends TranslationVisitor {
public static final String CLASS_PATH = "net/ME1312/CBS/EmulatedExtension";
public static final String CLASS_NAME = CLASS_PATH.replace('/', '.');
public static final int CLASS_VERSION = V1_8;

private static final String EMU_PATH = Type.getInternalName(EmulatedPlayer.class);
private static final String EMU_DESC = Type.getDescriptor(EmulatedPlayer.class);
private static final String EMU_FIELD = "$";
private static final String EMU_METHOD = EMU_FIELD;

public static final boolean DEBUG = Boolean.getBoolean("cbs.debug");
private static final String DEBUG_FIELD = "debug";
private static final String DEBUG_METHOD = EMU_METHOD;

private final HashSet<String> classes;
private final HashSet<String> methods;
private final ClassWriter cv;
private final Logger log;
private boolean spaced;
public PlayerVisitor() {
super(ASM9);
classes = new HashSet<>();
private boolean flip;
public PlayerVisitor() throws IOException {
flip = false;
scan(EmulatedPlayer.class);
classes.clear();
methods = new HashSet<>();
log = Bukkit.getLogger();
stage = "implementable methods";
flip = true;
cv = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cv.visit(CLASS_VERSION, ACC_PUBLIC | ACC_FINAL, CLASS_PATH, null, "java/lang/Object", new String[] { Type.getInternalName(Player.class) });
cv.visitField(ACC_PRIVATE | ACC_FINAL, EMU_FIELD, EMU_DESC, null, null);
Expand All @@ -51,221 +48,126 @@ public PlayerVisitor() {
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(DUP_X2);
mv.visitFieldInsn(PUTFIELD, CLASS_PATH, EMU_FIELD, EMU_DESC);
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, EMU_METHOD, '(' + Type.getDescriptor(Player.class) + ")V", false);
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, HIDDEN_METHOD, '(' + Type.getDescriptor(Player.class) + ")V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}

private void log(String msg, String clazz) {
log.warning("CBS > " + msg + clazz);
spaced = false;
}

public PlayerVisitor scan(Class<?> clazz) throws IOException {
String path = clazz.getCanonicalName().replace('.', '/');
if (classes.add(path)) {
scan(clazz, path);
}
public PlayerVisitor translate(Class<?> clazz) throws IOException {
scan(clazz);
return this;
}

private void scan(String path) throws IOException {
if (classes.add(path)) {
try {
scan(Class.forName(path.replace('/', '.')), path);
} catch (ClassNotFoundException e) {
if (DEBUG) log("Failed to locate class: ", path.replace('/', '.'));
}
}
}

private void scan(Class<?> clazz, String path) throws IOException {
InputStream stream = clazz.getResourceAsStream('/' + path + ".class");
if (stream != null) {
ClassReader reader = new ClassReader(stream);
reader.accept(this, 0);
} else if (DEBUG) {
log("Failed to locate classfile: ", path.replace('/', '.'));
}
}

@Override
public void visit(int version, int access, String name, String signature, String extended, String[] implemented) {
try {
if (implemented != null) for (String s : implemented) scan(s);
if (extended != null) scan(extended);
if (DEBUG) {
if (!spaced) {
log.info("");
log.info("");
}
log.info("CBS > Scanning class for implementable methods: " + name.replace('/', '.'));
}
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void visitEnd() {
if (DEBUG) {
log.info("");
log.info("");
spaced = true;
}
}

public byte[] flip() {
return cv.toByteArray();
}

private static Class<?> translate(Type type) throws ClassNotFoundException {
switch(type.getSort()) {
case Type.VOID:
return void.class;
case Type.BOOLEAN:
return boolean.class;
case Type.CHAR:
return char.class;
case Type.BYTE:
return byte.class;
case Type.SHORT:
return short.class;
case Type.INT:
return int.class;
case Type.FLOAT:
return float.class;
case Type.LONG:
return long.class;
case Type.DOUBLE:
return double.class;
case Type.ARRAY:
return Array.newInstance(translate(type.getElementType()), 0).getClass();
case Type.OBJECT:
return Class.forName(type.getClassName());
default:
throw new AssertionError();
}
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if ((access & ACC_STATIC) == 0 && (access & ACC_FINAL) == 0) {
String status = "Merged: ";
if (methods.add(name + descriptor)) {
Type method = Type.getMethodType(descriptor);
Type returns = method.getReturnType();
Type[] params = method.getArgumentTypes();
String translation = null;
boolean debugging = true;
boolean translated = false;
final int length = params.length;
status = "Skipped: ";
if (flip) {
if ((access & ACC_STATIC) == 0 && (access & ACC_FINAL) == 0) {
String status = "Merged: ";
if (methods.add(name + descriptor)) {
Type method = Type.getMethodType(descriptor);
Type returns = method.getReturnType();
Type[] params = method.getArgumentTypes();
Translation translation = translations.get(identify(name, descriptor));
final boolean translated = translation != null;
final int length = params.length;
status = "Skipped: ";

// Detect forwardable methods
try {
Class<?>[] args = new Class[length];
for (int i = 0; i < length; ++i) {
args[i] = translate(params[i]);
}
Method m = EmulatedPlayer.class.getMethod(name, args);
debugging = !m.isAnnotationPresent(SuppressDebugging.class);
translation = Type.getMethodDescriptor(m);
translated = true;
} catch (NoSuchMethodException | NoSuchMethodError e) {
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// Insert debugging method
if (translated || (access & ACC_ABSTRACT) != 0) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, name, descriptor, signature, exceptions);
mv.visitLabel(new Label());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, CLASS_PATH, EMU_FIELD, EMU_DESC);
if (!translated || translation.debugging) {
if (translated) mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, EMU_PATH, DEBUG_FIELD, "Z");
Label orElse = new Label();
mv.visitJumpInsn(IFEQ, orElse);
if (translated) {
mv.visitInsn(DUP);
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, CLASS_PATH, EMU_FIELD, EMU_DESC);
}
mv.visitInsn((translated)? ICONST_1 : ICONST_0);
xldc(mv, returns);
xpush(mv, length);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Class.class));
for (int i = 0; i < length; ++i) {
mv.visitInsn(DUP);
xpush(mv, i);
xldc(mv, params[i]);
mv.visitInsn(AASTORE);
}
xpush(mv, length);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class));
for (int a = 1, i = 0; i < length; ++i) {
mv.visitInsn(DUP);
xpush(mv, i);
a += xload(mv, params[i], a, true);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, HIDDEN_METHOD, "(ZLjava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Object;)V", false);
mv.visitLabel(orElse);
}

// Insert debugging method
if (translated || (access & ACC_ABSTRACT) != 0) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, name, descriptor, signature, exceptions);
mv.visitLabel(new Label());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, CLASS_PATH, EMU_FIELD, EMU_DESC);
if (debugging) {
if (translated) mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, EMU_PATH, DEBUG_FIELD, "Z");
Label orElse = new Label();
mv.visitJumpInsn(IFEQ, orElse);
// Handle method translation
if (translated) {
mv.visitInsn(DUP);
for (int a = 1, i = 0; i < length; ++i) {
a += xload(mv, params[i], a, false);
}
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, name, translation.descriptor, false);
status = "Translated: ";
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, CLASS_PATH, EMU_FIELD, EMU_DESC);
status = "Defaulted: ";
}
mv.visitInsn((translated)? ICONST_1 : ICONST_0);
xldc(mv, returns);
xpush(mv, length);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Class.class));
for (int i = 0; i < length; ++i) {
mv.visitInsn(DUP);
xpush(mv, i);
xldc(mv, params[i]);
mv.visitInsn(AASTORE);
switch (returns.getSort()) {
case Type.VOID:
mv.visitInsn(RETURN);
break;
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
if (!translated) mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
break;
case Type.FLOAT:
if (!translated) mv.visitInsn(FCONST_0);
mv.visitInsn(FRETURN);
break;
case Type.LONG:
if (!translated) mv.visitInsn(LCONST_0);
mv.visitInsn(LRETURN);
break;
case Type.DOUBLE:
if (!translated) mv.visitInsn(DCONST_0);
mv.visitInsn(DRETURN);
break;
case Type.ARRAY:
case Type.OBJECT:
if (!translated) mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
break;
default:
throw new AssertionError();
}
xpush(mv, length);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class));
for (int a = 1, i = 0; i < length; ++i) {
mv.visitInsn(DUP);
xpush(mv, i);
a += xload(mv, params[i], a, true);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, DEBUG_METHOD, "(ZLjava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Object;)V", false);
mv.visitLabel(orElse);
}

// Handle method translation
if (translated) {
for (int a = 1, i = 0; i < length; ++i) {
a += xload(mv, params[i], a, false);
}
mv.visitMethodInsn(INVOKEVIRTUAL, EMU_PATH, name, translation, false);
status = "Translated: ";
} else {
status = "Defaulted: ";
}
switch (returns.getSort()) {
case Type.VOID:
mv.visitInsn(RETURN);
break;
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
if (!translated) mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
break;
case Type.FLOAT:
if (!translated) mv.visitInsn(FCONST_0);
mv.visitInsn(FRETURN);
break;
case Type.LONG:
if (!translated) mv.visitInsn(LCONST_0);
mv.visitInsn(LRETURN);
break;
case Type.DOUBLE:
if (!translated) mv.visitInsn(DCONST_0);
mv.visitInsn(DRETURN);
break;
case Type.ARRAY:
case Type.OBJECT:
if (!translated) mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
break;
default:
throw new AssertionError();
mv.visitMaxs(0, 0);
mv.visitEnd();
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
if (DEBUG) log.info(status + name + descriptor);
}
if (DEBUG) log.info(status + name + descriptor);
return null;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
return null;
}

private static void xpush(MethodVisitor mv, int i) {
Expand Down
Loading

0 comments on commit 9e66b8c

Please sign in to comment.