geforkt von Mirrors/Paper
Use ASM for event executors.
Uses method handles for private or static methods.
Dieser Commit ist enthalten in:
Ursprung
258a2ce7b8
Commit
844bc6c46a
@ -61,6 +61,9 @@ dependencies {
|
|||||||
apiAndDocs("net.kyori:adventure-text-serializer-legacy")
|
apiAndDocs("net.kyori:adventure-text-serializer-legacy")
|
||||||
apiAndDocs("net.kyori:adventure-text-serializer-plain")
|
apiAndDocs("net.kyori:adventure-text-serializer-plain")
|
||||||
apiAndDocs("net.kyori:adventure-text-logger-slf4j")
|
apiAndDocs("net.kyori:adventure-text-logger-slf4j")
|
||||||
|
|
||||||
|
implementation("org.ow2.asm:asm:9.7.1")
|
||||||
|
implementation("org.ow2.asm:asm-commons:9.7.1")
|
||||||
// Paper end
|
// Paper end
|
||||||
|
|
||||||
compileOnly("org.apache.maven:maven-resolver-provider:3.9.6")
|
compileOnly("org.apache.maven:maven-resolver-provider:3.9.6")
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.destroystokyo.paper.event.executor;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.util.SneakyThrow;
|
||||||
|
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;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
public class MethodHandleEventExecutor implements EventExecutor {
|
||||||
|
|
||||||
|
private final Class<? extends Event> eventClass;
|
||||||
|
private final MethodHandle handle;
|
||||||
|
|
||||||
|
public MethodHandleEventExecutor(final Class<? extends Event> eventClass, final MethodHandle handle) {
|
||||||
|
this.eventClass = eventClass;
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodHandleEventExecutor(final Class<? extends Event> eventClass, final Method m) {
|
||||||
|
this.eventClass = eventClass;
|
||||||
|
try {
|
||||||
|
m.setAccessible(true);
|
||||||
|
this.handle = MethodHandles.lookup().unreflect(m);
|
||||||
|
} catch (final IllegalAccessException e) {
|
||||||
|
throw new AssertionError("Unable to set accessible", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final Listener listener, final Event event) throws EventException {
|
||||||
|
if (!this.eventClass.isInstance(event)) return;
|
||||||
|
try {
|
||||||
|
this.handle.invoke(listener, event);
|
||||||
|
} catch (final Throwable t) {
|
||||||
|
SneakyThrow.sneaky(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.destroystokyo.paper.event.executor;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.util.SneakyThrow;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.EventException;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.plugin.EventExecutor;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
public class StaticMethodHandleEventExecutor implements EventExecutor {
|
||||||
|
|
||||||
|
private final Class<? extends Event> eventClass;
|
||||||
|
private final MethodHandle handle;
|
||||||
|
|
||||||
|
public StaticMethodHandleEventExecutor(final Class<? extends Event> eventClass, final 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 (final IllegalAccessException e) {
|
||||||
|
throw new AssertionError("Unable to set accessible", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final Listener listener, final Event event) throws EventException {
|
||||||
|
if (!this.eventClass.isInstance(event)) return;
|
||||||
|
try {
|
||||||
|
this.handle.invoke(event);
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
SneakyThrow.sneaky(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
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.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
||||||
|
import static org.objectweb.asm.Opcodes.V1_8;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
public final class ASMEventExecutorGenerator {
|
||||||
|
|
||||||
|
private static final String EXECUTE_DESCRIPTOR = "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V";
|
||||||
|
|
||||||
|
public static byte[] generateEventExecutor(final Method m, final String name) {
|
||||||
|
final 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", EXECUTE_DESCRIPTOR, null, null), ACC_PUBLIC, "execute", EXECUTE_DESCRIPTOR);
|
||||||
|
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());
|
||||||
|
// The only purpose of this switch statement is to generate the correct pop instruction, should the event handler method return something other than void.
|
||||||
|
// Non-void event handlers will be unsupported in a future release.
|
||||||
|
switch (Type.getType(m.getReturnType()).getSize()) {
|
||||||
|
// case 0 is omitted because the only type that has size 0 is void - no pop instruction needed.
|
||||||
|
case 1 -> methodGenerator.pop(); // handles reference types and most primitives
|
||||||
|
case 2 -> methodGenerator.pop2(); // handles long and double
|
||||||
|
}
|
||||||
|
methodGenerator.returnValue();
|
||||||
|
methodGenerator.endMethod();
|
||||||
|
writer.visitEnd();
|
||||||
|
return writer.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AtomicInteger NEXT_ID = new AtomicInteger(1);
|
||||||
|
|
||||||
|
public static String generateName() {
|
||||||
|
final int id = NEXT_ID.getAndIncrement();
|
||||||
|
return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.destroystokyo.paper.event.executor.asm;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
public interface ClassDefiner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the defined classes can bypass access checks
|
||||||
|
*
|
||||||
|
* @return if classes bypass access checks
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
Class<?> defineClass(ClassLoader parentLoader, String name, byte[] data);
|
||||||
|
|
||||||
|
static ClassDefiner getInstance() {
|
||||||
|
return SafeClassDefiner.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.destroystokyo.paper.event.executor.asm;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
public class SafeClassDefiner implements ClassDefiner {
|
||||||
|
|
||||||
|
/* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner();
|
||||||
|
|
||||||
|
private SafeClassDefiner() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConcurrentMap<ClassLoader, GeneratedClassLoader> loaders = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final ClassLoader parentLoader, final String name, final byte[] data) {
|
||||||
|
final GeneratedClassLoader loader = this.loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new);
|
||||||
|
synchronized (loader.getClassLoadingLock(name)) {
|
||||||
|
Preconditions.checkState(!loader.hasClass(name), "%s already defined", name);
|
||||||
|
final Class<?> c = loader.define(name, data);
|
||||||
|
assert c.getName().equals(name);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GeneratedClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClassLoader.registerAsParallelCapable();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GeneratedClassLoader(final ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> define(final String name, final byte[] data) {
|
||||||
|
synchronized (this.getClassLoadingLock(name)) {
|
||||||
|
assert !this.hasClass(name);
|
||||||
|
final Class<?> c = this.defineClass(name, data, 0, data.length);
|
||||||
|
this.resolveClass(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getClassLoadingLock(final String name) {
|
||||||
|
return super.getClassLoadingLock(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasClass(final String name) {
|
||||||
|
synchronized (this.getClassLoadingLock(name)) {
|
||||||
|
try {
|
||||||
|
Class.forName(name);
|
||||||
|
return true;
|
||||||
|
} catch (final ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,75 @@ import org.bukkit.event.EventException;
|
|||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
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
|
* Interface which defines the class for event call backs to plugins
|
||||||
*/
|
*/
|
||||||
public interface EventExecutor {
|
public interface EventExecutor {
|
||||||
public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException;
|
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
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren