From f1583fcd7414bde1ee2efc2b1395a82cdb78b8a2 Mon Sep 17 00:00:00 2001 From: Jason <11360596+jpenilla@users.noreply.github.com> Date: Sat, 26 Nov 2022 10:36:11 -0700 Subject: [PATCH] Add `/paper dumplisteners tofile` and increase detail of command output (#8592) --- ...0409-Add-paper-dumplisteners-command.patch | 140 ++++++++++++++++++ ...0943-Add-paper-dumplisteners-command.patch | 75 ++++++++-- 2 files changed, 203 insertions(+), 12 deletions(-) diff --git a/patches/api/0409-Add-paper-dumplisteners-command.patch b/patches/api/0409-Add-paper-dumplisteners-command.patch index 3dd64e3012..e2d28d0fa2 100644 --- a/patches/api/0409-Add-paper-dumplisteners-command.patch +++ b/patches/api/0409-Add-paper-dumplisteners-command.patch @@ -4,6 +4,88 @@ Date: Sat, 19 Nov 2022 19:46:44 +0100 Subject: [PATCH] Add /paper dumplisteners command +diff --git a/src/main/java/co/aikar/timings/TimedEventExecutor.java b/src/main/java/co/aikar/timings/TimedEventExecutor.java +index 4e6e1b8e8aeb07e34536941d2cbfc25e5cfa6c27..34e43e56ccc663e05b9cae36643e8df5eee5cb17 100644 +--- a/src/main/java/co/aikar/timings/TimedEventExecutor.java ++++ b/src/main/java/co/aikar/timings/TimedEventExecutor.java +@@ -80,4 +80,10 @@ public class TimedEventExecutor implements EventExecutor { + executor.execute(listener, event); + } + } ++ ++ @Override ++ @NotNull ++ public String toString() { ++ return "TimedEventExecutor['" + this.executor.toString() + "']"; ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +index 5b28e9b1daba7834af67dbc193dd656bedd9a994..fbebf649e893cf872be9b27091146a7c2f451aca 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java +@@ -14,10 +14,12 @@ import org.jetbrains.annotations.NotNull; + public class MethodHandleEventExecutor implements EventExecutor { + private final Class<? extends Event> eventClass; + private final MethodHandle handle; ++ private final Method method; + + public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull MethodHandle handle) { + this.eventClass = eventClass; + this.handle = handle; ++ this.method = null; + } + + public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) { +@@ -28,6 +30,7 @@ public class MethodHandleEventExecutor implements EventExecutor { + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } ++ this.method = m; + } + + @Override +@@ -39,4 +42,10 @@ public class MethodHandleEventExecutor implements EventExecutor { + SneakyThrow.sneaky(t); + } + } ++ ++ @Override ++ @NotNull ++ public String toString() { ++ return "MethodHandleEventExecutor['" + this.method + "']"; ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +index c83672427324bd068ed52916f700b68446a226f6..87ea5354808dbbdefbdfc78f352a543f72a0d033 100644 +--- a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java ++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java +@@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull; + public class StaticMethodHandleEventExecutor implements EventExecutor { + private final Class<? extends Event> eventClass; + private final MethodHandle handle; ++ private final Method method; + + public StaticMethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) { + Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m); +@@ -29,6 +30,7 @@ public class StaticMethodHandleEventExecutor implements EventExecutor { + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } ++ this.method = m; + } + + @Override +@@ -40,4 +42,10 @@ public class StaticMethodHandleEventExecutor implements EventExecutor { + SneakyThrow.sneaky(throwable); + } + } ++ ++ @Override ++ @NotNull ++ public String toString() { ++ return "StaticMethodHandleEventExecutor['" + this.method + "']"; ++ } + } diff --git a/src/main/java/org/bukkit/event/HandlerList.java b/src/main/java/org/bukkit/event/HandlerList.java index ed78cca71f83b296d082d0af147ca8d622c7606a..2292bd460ce2be113beb4ba6b4eb19350060f01c 100644 --- a/src/main/java/org/bukkit/event/HandlerList.java @@ -35,3 +117,61 @@ index ed78cca71f83b296d082d0af147ca8d622c7606a..2292bd460ce2be113beb4ba6b4eb1935 handlerslots = new EnumMap<EventPriority, ArrayList<RegisteredListener>>(EventPriority.class); for (EventPriority o : EventPriority.values()) { handlerslots.put(o, new ArrayList<RegisteredListener>()); +diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java +index e1860322ae0f3c35097d16767628744034941749..bdfa3b5f4ab5023d3e1b5c50bed885b6aa118a02 100644 +--- a/src/main/java/org/bukkit/plugin/EventExecutor.java ++++ b/src/main/java/org/bukkit/plugin/EventExecutor.java +@@ -70,9 +70,18 @@ public interface EventExecutor { + 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); ++ return new EventExecutor() { ++ @Override ++ public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { ++ if (!eventClass.isInstance(event)) return; ++ asmExecutor.execute(listener, event); ++ } ++ ++ @Override ++ @NotNull ++ public String toString() { ++ return "ASMEventExecutor['" + m + "']"; ++ } + }; + } catch (InstantiationException | IllegalAccessException e) { + throw new AssertionError("Unable to initialize generated event executor", e); +diff --git a/src/main/java/org/bukkit/plugin/RegisteredListener.java b/src/main/java/org/bukkit/plugin/RegisteredListener.java +index 419aec56b0e3fa8bcec2ea7f340caa3456b57d00..3b3d9642a8d63798dc28f2f8df77f0466451cbff 100644 +--- a/src/main/java/org/bukkit/plugin/RegisteredListener.java ++++ b/src/main/java/org/bukkit/plugin/RegisteredListener.java +@@ -78,4 +78,27 @@ public class RegisteredListener { + public boolean isIgnoringCancelled() { + return ignoreCancelled; + } ++ ++ // Paper start ++ /** ++ * Get the executor for this registration. ++ * ++ * @return executor ++ */ ++ @NotNull ++ public EventExecutor getExecutor() { ++ return this.executor; ++ } ++ ++ @Override ++ public String toString() { ++ return "RegisteredListener{" ++ + "plugin=\"" + this.plugin.getName() ++ + "\", listener=\"" + this.listener ++ + "\", executor=\"" + this.executor ++ + "\", priority=\"" + this.priority.name() + " (" + this.priority.getSlot() + ")" ++ + "\", ignoringCancelled=" + this.ignoreCancelled ++ + "}"; ++ } ++ // Paper end + } diff --git a/patches/server/0943-Add-paper-dumplisteners-command.patch b/patches/server/0943-Add-paper-dumplisteners-command.patch index 2d1e89e37c..a5ac06d05d 100644 --- a/patches/server/0943-Add-paper-dumplisteners-command.patch +++ b/patches/server/0943-Add-paper-dumplisteners-command.patch @@ -27,35 +27,44 @@ index 724592234e2a178a518f6ab7d09c3180780371a7..92154550b41b2e1d03deb1271b71bb3b .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b83fa0eeb4 +index 0000000000000000000000000000000000000000..c80485b9b0c189820687b3a8ff66a855a4b1efa7 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java -@@ -0,0 +1,120 @@ +@@ -0,0 +1,171 @@ +package io.papermc.paper.command.subcommands; + +import com.destroystokyo.paper.util.SneakyThrow; +import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.io.IOException; ++import java.io.PrintWriter; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Set; -+import java.util.stream.Stream; ++import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.RegisteredListener; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + ++import static net.kyori.adventure.text.Component.newline; +import static net.kyori.adventure.text.Component.space; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.WHITE; + +@DefaultQualifier(NonNull.class) +public final class DumpListenersCommand implements PaperSubcommand { @@ -73,13 +82,41 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8 + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ if (args.length >= 1 && args[0].equals("tofile")) { ++ this.dumpToFile(sender); ++ return true; ++ } + this.doDumpListeners(sender, args); + return true; + } + ++ private void dumpToFile(final CommandSender sender) { ++ final File file = new File("debug/listeners-" ++ + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ try (final PrintWriter writer = new PrintWriter(file)) { ++ for (final String eventClass : eventClassNames()) { ++ final HandlerList handlers; ++ try { ++ handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); ++ } catch (final ReflectiveOperationException e) { ++ continue; ++ } ++ if (handlers.getRegisteredListeners().length != 0) { ++ writer.println(eventClass); ++ } ++ for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { ++ writer.println(" - " + registeredListener); ++ } ++ } ++ } catch (final IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ sender.sendMessage(text("Dumped listeners to " + file, GREEN)); ++ } ++ + private void doDumpListeners(final CommandSender sender, final String[] args) { + if (args.length == 0) { -+ sender.sendMessage(text("Usage: /paper dumplisteners [className]", RED)); ++ sender.sendMessage(text("Usage: /paper dumplisteners tofile|<className>", RED)); + return; + } + @@ -93,12 +130,19 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8 + + sender.sendMessage(text("Listeners for " + args[0] + ":")); + -+ Stream.of(handlers.getRegisteredListeners()) -+ .map(listener -> text(listener.getPlugin().getName(), GREEN) ++ for (final RegisteredListener listener : handlers.getRegisteredListeners()) { ++ final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE) ++ .append(newline()) ++ .append(text("Listener: " + listener.getListener())) ++ .append(newline()) ++ .append(text("Executor: " + listener.getExecutor())) ++ .append(newline()) ++ .append(text("Ignoring cancelled: " + listener.isIgnoringCancelled())); ++ ++ sender.sendMessage(text(listener.getPlugin().getName(), GREEN) + .append(space()) -+ .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY) -+ .hoverEvent(text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", GRAY)))) -+ .forEach(sender::sendMessage); ++ .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY).hoverEvent(hoverText))); ++ } + + sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length)); + @@ -115,16 +159,23 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8 + @Override + public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + return switch (args.length) { -+ case 0 -> List.copyOf(getEventClasses()); -+ case 1 -> getEventClasses().stream() ++ case 0 -> suggestions(); ++ case 1 -> suggestions().stream() + .filter(clazz -> clazz.toLowerCase(Locale.ROOT).contains(args[0].toLowerCase(Locale.ROOT))) + .toList(); + default -> Collections.emptyList(); + }; + } + ++ private static List<String> suggestions() { ++ final List<String> ret = new ArrayList<>(); ++ ret.add("tofile"); ++ ret.addAll(eventClassNames()); ++ return ret; ++ } ++ + @SuppressWarnings("unchecked") -+ private static Set<String> getEventClasses() { ++ private static Set<String> eventClassNames() { + try { + return (Set<String>) EVENT_TYPES_HANDLE.invokeExact(); + } catch (final Throwable e) {