397 lines
		
	
	
		
			No EOL
		
	
	
		
			16 KiB
			
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			397 lines
		
	
	
		
			No EOL
		
	
	
		
			16 KiB
			
		
	
	
	
		
			Diff
		
	
	
	
	
	
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
						|
From: Techcable <Techcable@outlook.com>
 | 
						|
Date: Thu, 3 Mar 2016 13:20:33 -0700
 | 
						|
Subject: [PATCH] Use ASM for event executors.
 | 
						|
 | 
						|
Uses method handles for private or static methods.
 | 
						|
 | 
						|
diff --git a/pom.xml b/pom.xml
 | 
						|
index c6bdf44e..31b6f51b 100644
 | 
						|
--- a/pom.xml
 | 
						|
+++ b/pom.xml
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
             <version>1.3</version>
 | 
						|
             <scope>test</scope>
 | 
						|
         </dependency>
 | 
						|
+        <!-- ASM -->
 | 
						|
+        <dependency>
 | 
						|
+            <groupId>org.ow2.asm</groupId>
 | 
						|
+            <artifactId>asm-all</artifactId>
 | 
						|
+            <version>5.0.4</version>
 | 
						|
+        </dependency>
 | 
						|
     </dependencies>
 | 
						|
 
 | 
						|
     <build>
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..9ff99e3b
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.event.executor;
 | 
						|
+
 | 
						|
+import java.lang.invoke.MethodHandle;
 | 
						|
+import java.lang.invoke.MethodHandles;
 | 
						|
+import java.lang.reflect.Method;
 | 
						|
+
 | 
						|
+import org.bukkit.event.Event;
 | 
						|
+import org.bukkit.event.EventException;
 | 
						|
+import org.bukkit.event.Listener;
 | 
						|
+import org.bukkit.plugin.EventExecutor;
 | 
						|
+
 | 
						|
+public class MethodHandleEventExecutor implements EventExecutor {
 | 
						|
+    private final Class<? extends Event> eventClass;
 | 
						|
+    private final MethodHandle handle;
 | 
						|
+
 | 
						|
+    public MethodHandleEventExecutor(Class<? extends Event> eventClass, MethodHandle handle) {
 | 
						|
+        this.eventClass = eventClass;
 | 
						|
+        this.handle = handle;
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    public MethodHandleEventExecutor(Class<? extends Event> eventClass, Method m) {
 | 
						|
+        this.eventClass = eventClass;
 | 
						|
+        try {
 | 
						|
+            m.setAccessible(true);
 | 
						|
+            this.handle = MethodHandles.lookup().unreflect(m);
 | 
						|
+        } catch (IllegalAccessException e) {
 | 
						|
+            throw new AssertionError("Unable to set accessible", e);
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    @Override
 | 
						|
+    public void execute(Listener listener, Event event) throws EventException {
 | 
						|
+        if (!eventClass.isInstance(event)) return;
 | 
						|
+        try {
 | 
						|
+            handle.invoke(listener, event);
 | 
						|
+        } catch (Throwable t) {
 | 
						|
+            throw new EventException(t);
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..f60f0100
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.event.executor;
 | 
						|
+
 | 
						|
+import java.lang.invoke.MethodHandle;
 | 
						|
+import java.lang.invoke.MethodHandles;
 | 
						|
+import java.lang.reflect.Method;
 | 
						|
+import java.lang.reflect.Modifier;
 | 
						|
+
 | 
						|
+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;
 | 
						|
+
 | 
						|
+public class StaticMethodHandleEventExecutor implements EventExecutor {
 | 
						|
+    private final Class<? extends Event> eventClass;
 | 
						|
+    private final MethodHandle handle;
 | 
						|
+
 | 
						|
+    public StaticMethodHandleEventExecutor(Class<? extends Event> eventClass, Method m) {
 | 
						|
+        Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m);
 | 
						|
+        this.eventClass = eventClass;
 | 
						|
+        try {
 | 
						|
+            m.setAccessible(true);
 | 
						|
+            this.handle = MethodHandles.lookup().unreflect(m);
 | 
						|
+        } catch (IllegalAccessException e) {
 | 
						|
+            throw new AssertionError("Unable to set accessible", e);
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    @Override
 | 
						|
+    public void execute(Listener listener, Event event) throws EventException {
 | 
						|
+        if (!eventClass.isInstance(event)) return;
 | 
						|
+        try {
 | 
						|
+            handle.invoke(event);
 | 
						|
+        } catch (Throwable t) {
 | 
						|
+            throw new EventException(t);
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..140cf0ad
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.event.executor.asm;
 | 
						|
+
 | 
						|
+import java.lang.reflect.Method;
 | 
						|
+import java.util.concurrent.atomic.AtomicInteger;
 | 
						|
+
 | 
						|
+import org.bukkit.plugin.EventExecutor;
 | 
						|
+import org.objectweb.asm.ClassWriter;
 | 
						|
+import org.objectweb.asm.Type;
 | 
						|
+import org.objectweb.asm.commons.GeneratorAdapter;
 | 
						|
+
 | 
						|
+import static org.objectweb.asm.Opcodes.*;
 | 
						|
+
 | 
						|
+public class ASMEventExecutorGenerator {
 | 
						|
+    public static byte[] generateEventExecutor(Method m, String name) {
 | 
						|
+        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
 | 
						|
+        writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)});
 | 
						|
+        // Generate constructor
 | 
						|
+        GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null), ACC_PUBLIC, "<init>", "()V");
 | 
						|
+        methodGenerator.loadThis();
 | 
						|
+        methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false); // Invoke the super class (Object) constructor
 | 
						|
+        methodGenerator.returnValue();
 | 
						|
+        methodGenerator.endMethod();
 | 
						|
+        // Generate the execute method
 | 
						|
+        methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V");;
 | 
						|
+        methodGenerator.loadArg(0);
 | 
						|
+        methodGenerator.checkCast(Type.getType(m.getDeclaringClass()));
 | 
						|
+        methodGenerator.loadArg(1);
 | 
						|
+        methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0]));
 | 
						|
+        methodGenerator.visitMethodInsn(m.getDeclaringClass().isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface());
 | 
						|
+        if (m.getReturnType() != void.class) {
 | 
						|
+            methodGenerator.pop();
 | 
						|
+        }
 | 
						|
+        methodGenerator.returnValue();
 | 
						|
+        methodGenerator.endMethod();
 | 
						|
+        writer.visitEnd();
 | 
						|
+        return writer.toByteArray();
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    public static AtomicInteger NEXT_ID = new AtomicInteger(1);
 | 
						|
+    public static String generateName() {
 | 
						|
+        int id = NEXT_ID.getAndIncrement();
 | 
						|
+        return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id;
 | 
						|
+    }
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..6941d9fb
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.event.executor.asm;
 | 
						|
+
 | 
						|
+import com.destroystokyo.paper.utils.UnsafeUtils;
 | 
						|
+
 | 
						|
+public interface ClassDefiner {
 | 
						|
+
 | 
						|
+    /**
 | 
						|
+     * Returns if the defined classes can bypass access checks
 | 
						|
+     *
 | 
						|
+     * @return if classes bypass access checks
 | 
						|
+     */
 | 
						|
+    public default boolean isBypassAccessChecks() {
 | 
						|
+        return false;
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    /**
 | 
						|
+     * Define a class
 | 
						|
+     *
 | 
						|
+     * @param parentLoader the parent classloader
 | 
						|
+     * @param name         the name of the class
 | 
						|
+     * @param data         the class data to load
 | 
						|
+     * @return the defined class
 | 
						|
+     * @throws ClassFormatError     if the class data is invalid
 | 
						|
+     * @throws NullPointerException if any of the arguments are null
 | 
						|
+     */
 | 
						|
+    public Class<?> defineClass(ClassLoader parentLoader, String name, byte[] data);
 | 
						|
+
 | 
						|
+    public static ClassDefiner getInstance() {
 | 
						|
+        return SafeClassDefiner.INSTANCE;
 | 
						|
+    }
 | 
						|
+
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..776a9a03
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.event.executor.asm;
 | 
						|
+
 | 
						|
+import java.util.concurrent.ConcurrentHashMap;
 | 
						|
+import java.util.concurrent.ConcurrentMap;
 | 
						|
+
 | 
						|
+import com.google.common.base.Preconditions;
 | 
						|
+
 | 
						|
+import org.objectweb.asm.Type;
 | 
						|
+
 | 
						|
+public class SafeClassDefiner implements ClassDefiner {
 | 
						|
+    /* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner();
 | 
						|
+
 | 
						|
+    private SafeClassDefiner() {}
 | 
						|
+
 | 
						|
+    private final ConcurrentMap<ClassLoader, GeneratedClassLoader> loaders = new ConcurrentHashMap<>();
 | 
						|
+
 | 
						|
+    @Override
 | 
						|
+    public Class<?> defineClass(ClassLoader parentLoader, String name, byte[] data) {
 | 
						|
+        GeneratedClassLoader loader = loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new);
 | 
						|
+        synchronized (loader.getClassLoadingLock(name)) {
 | 
						|
+            Preconditions.checkState(!loader.hasClass(name), "%s already defined", name);
 | 
						|
+            Class<?> c = loader.define(name, data);
 | 
						|
+            assert c.getName().equals(name);
 | 
						|
+            return c;
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    private static class GeneratedClassLoader extends ClassLoader {
 | 
						|
+        static {
 | 
						|
+            ClassLoader.registerAsParallelCapable();
 | 
						|
+        }
 | 
						|
+
 | 
						|
+        protected GeneratedClassLoader(ClassLoader parent) {
 | 
						|
+            super(parent);
 | 
						|
+        }
 | 
						|
+
 | 
						|
+        private Class<?> define(String name, byte[] data) {
 | 
						|
+            synchronized (getClassLoadingLock(name)) {
 | 
						|
+                assert !hasClass(name);
 | 
						|
+                Class<?> c = defineClass(name, data, 0, data.length);
 | 
						|
+                resolveClass(c);
 | 
						|
+                return c;
 | 
						|
+            }
 | 
						|
+        }
 | 
						|
+
 | 
						|
+        @Override
 | 
						|
+        public Object getClassLoadingLock(String name) {
 | 
						|
+            return super.getClassLoadingLock(name);
 | 
						|
+        }
 | 
						|
+
 | 
						|
+        public boolean hasClass(String name) {
 | 
						|
+            synchronized (getClassLoadingLock(name)) {
 | 
						|
+                try {
 | 
						|
+                    Class.forName(name);
 | 
						|
+                    return true;
 | 
						|
+                } catch (ClassNotFoundException e) {
 | 
						|
+                    return false;
 | 
						|
+                }
 | 
						|
+            }
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java b/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java
 | 
						|
new file mode 100644
 | 
						|
index 00000000..62acbf82
 | 
						|
--- /dev/null
 | 
						|
+++ b/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java
 | 
						|
@@ -0,0 +0,0 @@
 | 
						|
+package com.destroystokyo.paper.utils;
 | 
						|
+
 | 
						|
+import sun.misc.Unsafe;
 | 
						|
+
 | 
						|
+import java.lang.reflect.Field;
 | 
						|
+
 | 
						|
+public class UnsafeUtils {
 | 
						|
+    private UnsafeUtils() {}
 | 
						|
+
 | 
						|
+    private static final Unsafe UNSAFE;
 | 
						|
+    static {
 | 
						|
+        Unsafe unsafe;
 | 
						|
+        try {
 | 
						|
+            Class c = Class.forName("sun.misc.Unsafe");
 | 
						|
+            Field f = c.getDeclaredField("theUnsafe");
 | 
						|
+            f.setAccessible(true);
 | 
						|
+            unsafe = (Unsafe) f.get(null);
 | 
						|
+        } catch (ClassNotFoundException | NoSuchFieldException | SecurityException e) {
 | 
						|
+            unsafe = null;
 | 
						|
+        } catch (IllegalAccessException e) {
 | 
						|
+            throw new AssertionError(e);
 | 
						|
+        }
 | 
						|
+        UNSAFE = unsafe;
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    public static boolean isUnsafeSupported() {
 | 
						|
+        return UNSAFE != null;
 | 
						|
+    }
 | 
						|
+
 | 
						|
+    public static Unsafe getUnsafe() {
 | 
						|
+        return UNSAFE;
 | 
						|
+    }
 | 
						|
+}
 | 
						|
diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java
 | 
						|
index 3b2c99ea..f9316d65 100644
 | 
						|
--- a/src/main/java/org/bukkit/plugin/EventExecutor.java
 | 
						|
+++ b/src/main/java/org/bukkit/plugin/EventExecutor.java
 | 
						|
@@ -0,0 +0,0 @@ import org.bukkit.event.Event;
 | 
						|
 import org.bukkit.event.EventException;
 | 
						|
 import org.bukkit.event.Listener;
 | 
						|
 
 | 
						|
+// Paper start
 | 
						|
+import java.lang.reflect.Method;
 | 
						|
+import java.lang.reflect.Modifier;
 | 
						|
+
 | 
						|
+import com.destroystokyo.paper.event.executor.MethodHandleEventExecutor;
 | 
						|
+import com.destroystokyo.paper.event.executor.StaticMethodHandleEventExecutor;
 | 
						|
+import com.destroystokyo.paper.event.executor.asm.ASMEventExecutorGenerator;
 | 
						|
+import com.destroystokyo.paper.event.executor.asm.ClassDefiner;
 | 
						|
+import com.google.common.base.Preconditions;
 | 
						|
+// Paper end
 | 
						|
+
 | 
						|
 /**
 | 
						|
  * Interface which defines the class for event call backs to plugins
 | 
						|
  */
 | 
						|
 public interface EventExecutor {
 | 
						|
     public void execute(Listener listener, Event event) throws EventException;
 | 
						|
+
 | 
						|
+    // Paper start
 | 
						|
+    public static EventExecutor create(Method m, Class<? extends Event> eventClass) {
 | 
						|
+        Preconditions.checkNotNull(m, "Null method");
 | 
						|
+        Preconditions.checkArgument(m.getParameterCount() != 0, "Incorrect number of arguments %s", m.getParameterCount());
 | 
						|
+        Preconditions.checkArgument(m.getParameterTypes()[0] == eventClass, "First parameter %s doesn't match event class %s", m.getParameterTypes()[0], eventClass);
 | 
						|
+        ClassDefiner definer = ClassDefiner.getInstance();
 | 
						|
+        if (Modifier.isStatic(m.getModifiers())) {
 | 
						|
+            return new StaticMethodHandleEventExecutor(eventClass, m);
 | 
						|
+        } if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) {
 | 
						|
+            String name = ASMEventExecutorGenerator.generateName();
 | 
						|
+            byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name);
 | 
						|
+            Class<? extends EventExecutor> c = definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class);
 | 
						|
+            try {
 | 
						|
+                EventExecutor asmExecutor = c.newInstance();
 | 
						|
+                // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception)
 | 
						|
+                return new EventExecutor() {
 | 
						|
+                    @Override
 | 
						|
+                    public void execute(Listener listener, Event event) throws EventException {
 | 
						|
+                        if (!eventClass.isInstance(event)) return;
 | 
						|
+                        try {
 | 
						|
+                            asmExecutor.execute(listener, event);
 | 
						|
+                        } catch (Exception e) {
 | 
						|
+                            throw new EventException(e);
 | 
						|
+                        }
 | 
						|
+                    }
 | 
						|
+                };
 | 
						|
+            } catch (InstantiationException | IllegalAccessException e) {
 | 
						|
+                throw new AssertionError("Unable to initialize generated event executor", e);
 | 
						|
+            }
 | 
						|
+        } else {
 | 
						|
+            return new MethodHandleEventExecutor(eventClass, m);
 | 
						|
+        }
 | 
						|
+    }
 | 
						|
+    // Paper end
 | 
						|
 }
 | 
						|
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
 | 
						|
index d8b9c244..40fd71dc 100644
 | 
						|
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
 | 
						|
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
 | 
						|
@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader {
 | 
						|
                 }
 | 
						|
             }
 | 
						|
 
 | 
						|
-            EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Spigot
 | 
						|
-                public void execute(Listener listener, Event event) throws EventException {
 | 
						|
-                    try {
 | 
						|
-                        if (!eventClass.isAssignableFrom(event.getClass())) {
 | 
						|
-                            return;
 | 
						|
-                        }
 | 
						|
-                        method.invoke(listener, event);
 | 
						|
-                    } catch (InvocationTargetException ex) {
 | 
						|
-                        throw new EventException(ex.getCause());
 | 
						|
-                    } catch (Throwable t) {
 | 
						|
-                        throw new EventException(t);
 | 
						|
-                    }
 | 
						|
-                }
 | 
						|
-            }, plugin, method, eventClass); // Spigot
 | 
						|
+            EventExecutor executor = new co.aikar.timings.TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); // Spigot // Paper - Use factory method `EventExecutor.create()`
 | 
						|
             if (false) { // Spigot - RL handles useTimings check now
 | 
						|
                 eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
 | 
						|
             } else {
 | 
						|
--
 |