aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch
diff options
context:
space:
mode:
Diffstat (limited to 'Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch')
-rw-r--r--Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch398
1 files changed, 398 insertions, 0 deletions
diff --git a/Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch b/Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch
new file mode 100644
index 0000000000..e54e92b274
--- /dev/null
+++ b/Spigot-API-Patches-Unmapped/0023-Use-ASM-for-event-executors.patch
@@ -0,0 +1,398 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Techcable <[email protected]>
+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 75b2830340051deb0fa39149e80872d2b88ed6f0..c3d65e441f5c26b6c6b10f4924504d8f3837e674 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -143,6 +143,17 @@
+ <version>9.1</version>
+ <scope>test</scope>
+ </dependency>
++ <!-- ASM -->
++ <dependency>
++ <groupId>org.ow2.asm</groupId>
++ <artifactId>asm</artifactId>
++ <version>9.0</version>
++ </dependency>
++ <dependency>
++ <groupId>org.ow2.asm</groupId>
++ <artifactId>asm-commons</artifactId>
++ <version>9.0</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 0000000000000000000000000000000000000000..5b28e9b1daba7834af67dbc193dd656bedd9a994
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
+@@ -0,0 +1,42 @@
++package com.destroystokyo.paper.event.executor;
++
++import java.lang.invoke.MethodHandle;
++import java.lang.invoke.MethodHandles;
++import java.lang.reflect.Method;
++
++import com.destroystokyo.paper.util.SneakyThrow;
++import org.bukkit.event.Event;
++import org.bukkit.event.EventException;
++import org.bukkit.event.Listener;
++import org.bukkit.plugin.EventExecutor;
++import org.jetbrains.annotations.NotNull;
++
++public class MethodHandleEventExecutor implements EventExecutor {
++ private final Class<? extends Event> eventClass;
++ private final MethodHandle handle;
++
++ public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull MethodHandle handle) {
++ this.eventClass = eventClass;
++ this.handle = handle;
++ }
++
++ public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull 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(@NotNull Listener listener, @NotNull Event event) throws EventException {
++ if (!eventClass.isInstance(event)) return;
++ try {
++ handle.invoke(listener, event);
++ } catch (Throwable t) {
++ SneakyThrow.sneaky(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 0000000000000000000000000000000000000000..c83672427324bd068ed52916f700b68446a226f6
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
+@@ -0,0 +1,43 @@
++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.destroystokyo.paper.util.SneakyThrow;
++import com.google.common.base.Preconditions;
++
++import org.bukkit.Bukkit;
++import org.bukkit.event.Event;
++import org.bukkit.event.EventException;
++import org.bukkit.event.Listener;
++import org.bukkit.plugin.EventExecutor;
++import org.jetbrains.annotations.NotNull;
++
++public class StaticMethodHandleEventExecutor implements EventExecutor {
++ private final Class<? extends Event> eventClass;
++ private final MethodHandle handle;
++
++ public StaticMethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) {
++ Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m);
++ Preconditions.checkArgument(eventClass != null, "eventClass is null");
++ 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(@NotNull Listener listener, @NotNull Event event) throws EventException {
++ if (!eventClass.isInstance(event)) return;
++ try {
++ handle.invoke(event);
++ } catch (Throwable throwable) {
++ SneakyThrow.sneaky(throwable);
++ }
++ }
++}
+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 0000000000000000000000000000000000000000..b6e7d8ee8d903ebf975d60bec0e08603d9a49fdb
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
+@@ -0,0 +1,47 @@
++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.jetbrains.annotations.NotNull;
++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 {
++ @NotNull
++ public static byte[] generateEventExecutor(@NotNull Method m, @NotNull 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);
++ @NotNull
++ 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 0000000000000000000000000000000000000000..f79685b48bb581277a6891927988b6f7a4389dc4
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
+@@ -0,0 +1,34 @@
++package com.destroystokyo.paper.event.executor.asm;
++
++import org.jetbrains.annotations.NotNull;
++
++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
++ */
++ @NotNull
++ public Class<?> defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull byte[] data);
++
++ @NotNull
++ 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 0000000000000000000000000000000000000000..ac99477e9f2c08041aeff31abc1d1edee58d0a67
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
+@@ -0,0 +1,66 @@
++package com.destroystokyo.paper.event.executor.asm;
++
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ConcurrentMap;
++
++import com.google.common.base.Preconditions;
++
++import com.google.common.collect.MapMaker;
++import org.jetbrains.annotations.NotNull;
++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 MapMaker().weakKeys().makeMap();
++
++ @NotNull
++ @Override
++ public Class<?> defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull 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(@NotNull ClassLoader parent) {
++ super(parent);
++ }
++
++ private Class<?> define(@NotNull String name, byte[] data) {
++ synchronized (getClassLoadingLock(name)) {
++ assert !hasClass(name);
++ Class<?> c = defineClass(name, data, 0, data.length);
++ resolveClass(c);
++ return c;
++ }
++ }
++
++ @Override
++ @NotNull
++ public Object getClassLoadingLock(@NotNull String name) {
++ return super.getClassLoadingLock(name);
++ }
++
++ public boolean hasClass(@NotNull String name) {
++ synchronized (getClassLoadingLock(name)) {
++ try {
++ Class.forName(name);
++ return true;
++ } catch (ClassNotFoundException e) {
++ return false;
++ }
++ }
++ }
++ }
++}
+diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java
+index a850f0780de05463fc0d3f9e15ff7f19d88b2aed..9026e108ccd3a88aee1267ee275137befa646455 100644
+--- a/src/main/java/org/bukkit/plugin/EventExecutor.java
++++ b/src/main/java/org/bukkit/plugin/EventExecutor.java
+@@ -5,9 +5,75 @@ import org.bukkit.event.EventException;
+ import org.bukkit.event.Listener;
+ import org.jetbrains.annotations.NotNull;
+
++// Paper start
++import java.lang.reflect.Method;
++import java.lang.reflect.Modifier;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ConcurrentMap;
++import java.util.function.Function;
++
++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(@NotNull Listener listener, @NotNull Event event) throws EventException;
++
++ // Paper start
++ ConcurrentMap<Method, Class<? extends EventExecutor>> eventExecutorMap = new ConcurrentHashMap<Method, Class<? extends EventExecutor>>() {
++ @NotNull
++ @Override
++ public Class<? extends EventExecutor> computeIfAbsent(@NotNull Method key, @NotNull Function<? super Method, ? extends Class<? extends EventExecutor>> mappingFunction) {
++ Class<? extends EventExecutor> executorClass = get(key);
++ if (executorClass != null)
++ return executorClass;
++
++ //noinspection SynchronizationOnLocalVariableOrMethodParameter
++ synchronized (key) {
++ executorClass = get(key);
++ if (executorClass != null)
++ return executorClass;
++
++ return super.computeIfAbsent(key, mappingFunction);
++ }
++ }
++ };
++
++ @NotNull
++ public static EventExecutor create(@NotNull Method m, @NotNull 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);
++ } else if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) {
++ // get the existing generated EventExecutor class for the Method or generate one
++ Class<? extends EventExecutor> executorClass = eventExecutorMap.computeIfAbsent(m, (__) -> {
++ String name = ASMEventExecutorGenerator.generateName();
++ byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name);
++ return definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class);
++ });
++
++ try {
++ EventExecutor asmExecutor = executorClass.newInstance();
++ // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception)
++ return (listener, event) -> {
++ if (!eventClass.isInstance(event)) return;
++ asmExecutor.execute(listener, event);
++ };
++ } 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 c8497cb3021f584a885f4cb21c3be576ce0935a7..5be6460e8eb81381c7e305cb7ab6b77c0c7a8fe5 100644
+--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+@@ -300,21 +300,7 @@ public final class JavaPluginLoader implements PluginLoader {
+ }
+ }
+
+- EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Paper
+- @Override
+- public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { // Paper
+- 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); // Paper
++ EventExecutor executor = new co.aikar.timings.TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); // Paper // Paper (Yes.) - 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 {