Skip to content

Instantly share code, notes, and snippets.

@Techcable
Created September 2, 2015 06:30
Show Gist options
  • Select an option

  • Save Techcable/0a8035cdd39b1dee9cc0 to your computer and use it in GitHub Desktop.

Select an option

Save Techcable/0a8035cdd39b1dee9cc0 to your computer and use it in GitHub Desktop.
ASM Bukkit Executors
package net.techcable.tacospigot;
import com.google.common.base.Preconditions;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.atomic.AtomicInteger;
import static org.objectweb.asm.Opcodes.*;
public class EventExecutorFactory {
private EventExecutorFactory() {
}
public static final String INTERNAL_NAME = Type.getInternalName(EventExecutorFactory.class);
public static final String GENERATED_PACKAGE = INTERNAL_NAME.substring(0, INTERNAL_NAME.lastIndexOf('/')) + "/generated";
public static final String GENERATED_EXECUTOR_PREFIX = GENERATED_PACKAGE + "/GeneratedEventExecutor";
public static final Method EXECUTE_METHOD;
public static final String EXECUTE_DESCRIPTOR;
static {
try {
EXECUTE_METHOD = EventExecutor.class.getMethod("execute", Listener.class, Event.class);
} catch (NoSuchMethodException e) {
throw new AssertionError("Could not find execute(Listener, Event)V in EventExecutor");
}
EXECUTE_DESCRIPTOR = Type.getMethodDescriptor(EXECUTE_METHOD);
}
private static final AtomicInteger nextId = new AtomicInteger();
@sun.reflect.CallerSensitive
public static EventExecutor newASMEventExecutor(Method method) {
validate(method);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
String name = GENERATED_EXECUTOR_PREFIX + nextId.incrementAndGet();
writer.visit(V1_6, ACC_PUBLIC, name, null, Type.getInternalName(Object.class), new String[]{EventExecutor.INTERNAL_NAME});
// Generate a constructor
MethodVisitor methodWriter = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodWriter.visitCode();
methodWriter.visitVarInsn(ALOAD, 0); // Get this/super reference
methodWriter.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false); // Invoke the super class (Object) constructor
methodWriter.visitInsn(RETURN);
methodWriter.visitMaxs(0, 0); // Auto-generated
methodWriter.visitEnd();
// Generate execute(Listener, Event)V throws EventException
methodWriter = writer.visitMethod(ACC_PUBLIC + ACC_FINAL, "execute", EXECUTE_DESCRIPTOR, null, new String[]{EventExecutor.INTERNAL_NAME});
methodWriter.visitCode();
generateExecute(methodWriter, method); // Generate the code
methodWriter.visitMaxs(0, 0); // Auto-generated
methodWriter.visitEnd();
// Load using the class loader
ClassLoader parent;
try {
parent = sun.reflect.Reflection.getCallerClass().getClassLoader();
} catch (Throwable t) {
Class<?>[] stack = new SecurityManager() {
@Override
public Class[] getClassContext() {
return super.getClassContext();
}
}.getClassContext();
parent = stack[1].getClassLoader();
}
GeneratedClassLoader loader = GeneratedClassLoader.getLoader(parent);
Class<? extends EventExecutor> clazz = loader.define(writer, name).asSubclass(EventExecutor.class);
try {
return clazz.newInstance();
} catch (Exception e) {
throw new AssertionError("Generated constructor must be public and not throw exceptions", e);
}
}
private static void generateExecute(MethodVisitor writer, Method method) {
Class<? extends Listener> listenerClass = method.getDeclaringClass().asSubclass(Listener.class);
Class<? extends Event> eventClass = method.getParameterTypes()[0].asSubclass(Event.class);
Label startTryBlock = new Label();
Label endTryBlock = new Label();
Label startCatchBlock = new Label();
Label endCatchBlock = new Label();
final int arg1Id = 1;
final int arg2Id = 2;
final int originalVarId = 3;
writer.visitTryCatchBlock(startTryBlock, endTryBlock, startCatchBlock, Type.getInternalName(Throwable.class)); // try {} catch (Throwable t)
writer.visitLabel(startTryBlock); // try {
writer.visitVarInsn(ALOAD, arg1Id); // Load listener onto the stack
writer.visitTypeInsn(CHECKCAST, Type.getInternalName(listenerClass)); // Check (Listener listener) instanceof method.getDeclaringClass()
writer.visitVarInsn(ALOAD, arg2Id); // Load event onto the stack
writer.visitTypeInsn(CHECKCAST, Type.getInternalName(eventClass)); // Check [Listener](Event event) instanceof eventClass
String ownerName = Type.getInternalName(method.getDeclaringClass());
String methodName = method.getName();
String descriptor = Type.getMethodDescriptor(method);
writer.visitMethodInsn(INVOKEVIRTUAL, ownerName, methodName, descriptor, method.getDeclaringClass().isInterface()); // Invoke the given method with (Listener listener, Event event)
writer.visitInsn(RETURN);
writer.visitLabel(endTryBlock); // end try
writer.visitLabel(startCatchBlock); // catch (Throwable original) {
// Now the only thingy on the stack should be (Throwable original)
writer.visitVarInsn(ASTORE, originalVarId); // Store (Throwable original)
writer.visitTypeInsn(NEW, EventException.INTERNAL_NAME); // Create a new instance of EventException
writer.visitInsn(DUP); // Duplicate (EventException exception) to (EventException, EventException exception)
writer.visitVarInsn(ALOAD, originalVarId); // Load original exception (Event
writer.visitMethodInsn(INVOKESPECIAL, EventException.INTERNAL_NAME, "<init>", "(Ljava/lang/Throwable;)V", false); // Call the constructor with [EventException](EventException exception, Throwable original
writer.visitInsn(ATHROW); // Throw (EventException exception)
writer.visitLabel(endCatchBlock); // end catch
}
public static EventExecutor newJREEventExecutor(Method method) {
validate(method);
Class<? extends Listener> listenerClass = method.getDeclaringClass().asSubclass(Listener.class);
Class<? extends Event> eventClass = method.getParameterTypes()[0].asSubclass(Event.class);
return new JREEventExecutor(method, listenerClass, eventClass);
}
private static void validate(Method method) {
Preconditions.checkArgument(Listener.class.isAssignableFrom(method.getDeclaringClass()), "The declaring class (%s) is not a Listener", method.getDeclaringClass().getName());
Preconditions.checkArgument(method.getParameterCount() == 1, "More than one parameter");
Preconditions.checkArgument(Event.class.isAssignableFrom(method.getParameterTypes()[0]), "The first parameter (%s) is not an Event", method.getParameterTypes()[0].getName());
}
public static EventExecutor newEventExecutor(Method method) {
boolean isPublic = Modifier.isPublic(method.getModifiers());
return isUseAsmForEvents() && isPublic ? newASMEventExecutor(method) : newJREEventExecutor(method);
}
private static class JREEventExecutor implements EventExecutor {
private final Method method;
private final Class<? extends Listener> listenerClass;
private final Class<? extends Event> eventClass;
public JREEventExecutor(Method method, Class<? extends Listener> listenerClass, Class<? extends Event> eventClass) {
this.method = method;
this.listenerClass = listenerClass;
this.eventClass = eventClass;
}
@Override
public final void execute(Listener listener, Event event) throws EventException {
listener = listenerClass.cast(listener); // Check cast
event = eventClass.cast(event); // Check cast
try {
method.invoke(listener, event);
} catch (IllegalAccessException e) {
throw new AssertionError("setAccessible() did not work", e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause == null) cause = e;
throw new EventException(cause);
}
}
}
// Command line options and overrides
private static int useAsmForEvents = -1;
public static boolean isUseAsmForEvents() {
if (useAsmForEvents == 0) return true;
else if (useAsmForEvents == 1) return false;
else {
String property = System.getProperty("taco.use-asm-for-events", "true");
setUseAsmForEvents(Boolean.parseBoolean(property));
return isUseAsmForEvents();
}
}
public static void setUseAsmForEvents(boolean asm) {
useAsmForEvents = asm ? 0 : 1;
}
public static void unsetUseAsmForEvents() {
useAsmForEvents = -1;
}
}
package net.techcable.tacospigot;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class GeneratedClassLoader extends ClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public GeneratedClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> define(ClassWriter writer, String className) {
className = className.replace('/', '.');
byte[] bytes = writer.toByteArray();
synchronized (getClassLoadingLock(className)) {
Class<?> clazz = defineClass(className, bytes, 0, bytes.length);
resolveClass(clazz);
return clazz;
}
}
private static GeneratedClassLoader lastLoader;
public static GeneratedClassLoader getLoader(ClassLoader parent) {
GeneratedClassLoader loader = GeneratedClassLoader.lastLoader;
if (loader != null && loader.getParent() == parent) return loader;
loader = new GeneratedClassLoader(parent);
GeneratedClassLoader.lastLoader = loader;
return loader;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment