Created
September 2, 2015 06:30
-
-
Save Techcable/0a8035cdd39b1dee9cc0 to your computer and use it in GitHub Desktop.
ASM Bukkit Executors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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