diff options
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.patch | 398 |
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 { |