From 8aff07afb0861a33c46c161518c88a3a64a0ab3c Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 23 Nov 2022 23:28:38 +0100 Subject: [PATCH] Add /paper dumplisteners command (#8507) Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Co-authored-by: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> --- ...0409-Add-paper-dumplisteners-command.patch | 37 +++++ ...0943-Add-paper-dumplisteners-command.patch | 153 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 patches/api/0409-Add-paper-dumplisteners-command.patch create mode 100644 patches/server/0943-Add-paper-dumplisteners-command.patch diff --git a/patches/api/0409-Add-paper-dumplisteners-command.patch b/patches/api/0409-Add-paper-dumplisteners-command.patch new file mode 100644 index 0000000000..3dd64e3012 --- /dev/null +++ b/patches/api/0409-Add-paper-dumplisteners-command.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Sat, 19 Nov 2022 19:46:44 +0100 +Subject: [PATCH] Add /paper dumplisteners command + + +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 ++++ b/src/main/java/org/bukkit/event/HandlerList.java +@@ -33,6 +33,13 @@ public class HandlerList { + */ + private static ArrayList allLists = new ArrayList(); + ++ // Paper start ++ /** ++ * Event types which have instantiated a {@link HandlerList}. ++ */ ++ private static final java.util.Set EVENT_TYPES = java.util.concurrent.ConcurrentHashMap.newKeySet(); ++ // Paper end ++ + /** + * Bake all handler lists. Best used just after all normal event + * registration is complete, ie just after all plugins are loaded if +@@ -94,6 +101,12 @@ public class HandlerList { + * The HandlerList is then added to meta-list for use in bakeAll() + */ + public HandlerList() { ++ // Paper start ++ java.lang.StackWalker.getInstance(java.util.EnumSet.of(java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE), 4) ++ .walk(s -> s.filter(f -> Event.class.isAssignableFrom(f.getDeclaringClass())).findFirst()) ++ .map(f -> f.getDeclaringClass().getName()) ++ .ifPresent(EVENT_TYPES::add); ++ // Paper end + handlerslots = new EnumMap>(EventPriority.class); + for (EventPriority o : EventPriority.values()) { + handlerslots.put(o, new ArrayList()); diff --git a/patches/server/0943-Add-paper-dumplisteners-command.patch b/patches/server/0943-Add-paper-dumplisteners-command.patch new file mode 100644 index 0000000000..2d1e89e37c --- /dev/null +++ b/patches/server/0943-Add-paper-dumplisteners-command.patch @@ -0,0 +1,153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Tue, 25 Oct 2022 21:15:37 +0200 +Subject: [PATCH] Add /paper dumplisteners command + +Co-authored-by: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 724592234e2a178a518f6ab7d09c3180780371a7..92154550b41b2e1d03deb1271b71bb3baa735e0a 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -2,6 +2,7 @@ package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.DumpItemCommand; ++import io.papermc.paper.command.subcommands.DumpListenersCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; +@@ -50,6 +51,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); + commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); ++ commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); + + return commands.entrySet().stream() + .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 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +@@ -0,0 +1,120 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import io.papermc.paper.command.PaperSubcommand; ++import java.lang.invoke.MethodHandle; ++import java.lang.invoke.MethodHandles; ++import java.lang.reflect.Field; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import java.util.Set; ++import java.util.stream.Stream; ++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.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++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; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpListenersCommand implements PaperSubcommand { ++ private static final MethodHandle EVENT_TYPES_HANDLE; ++ ++ static { ++ try { ++ final Field eventTypesField = HandlerList.class.getDeclaredField("EVENT_TYPES"); ++ eventTypesField.setAccessible(true); ++ EVENT_TYPES_HANDLE = MethodHandles.lookup().unreflectGetter(eventTypesField); ++ } catch (final ReflectiveOperationException e) { ++ throw new RuntimeException(e); ++ } ++ } ++ ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doDumpListeners(sender, args); ++ return true; ++ } ++ ++ private void doDumpListeners(final CommandSender sender, final String[] args) { ++ if (args.length == 0) { ++ sender.sendMessage(text("Usage: /paper dumplisteners [className]", RED)); ++ return; ++ } ++ ++ try { ++ final HandlerList handlers = (HandlerList) findClass(args[0]).getMethod("getHandlerList").invoke(null); ++ ++ if (handlers.getRegisteredListeners().length == 0) { ++ sender.sendMessage(text(args[0] + " does not have any registered listeners.")); ++ return; ++ } ++ ++ sender.sendMessage(text("Listeners for " + args[0] + ":")); ++ ++ Stream.of(handlers.getRegisteredListeners()) ++ .map(listener -> 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); ++ ++ sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length)); ++ ++ } catch (final ClassNotFoundException e) { ++ sender.sendMessage(text("Unable to find a class named '" + args[0] + "'. Make sure to use the fully qualified name.", RED)); ++ } catch (final NoSuchMethodException e) { ++ sender.sendMessage(text("Class '" + args[0] + "' does not have a valid getHandlerList method.", RED)); ++ } catch (final ReflectiveOperationException e) { ++ sender.sendMessage(text("Something went wrong, see the console for more details.", RED)); ++ MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class " + args[0], e); ++ } ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (args.length) { ++ case 0 -> List.copyOf(getEventClasses()); ++ case 1 -> getEventClasses().stream() ++ .filter(clazz -> clazz.toLowerCase(Locale.ROOT).contains(args[0].toLowerCase(Locale.ROOT))) ++ .toList(); ++ default -> Collections.emptyList(); ++ }; ++ } ++ ++ @SuppressWarnings("unchecked") ++ private static Set getEventClasses() { ++ try { ++ return (Set) EVENT_TYPES_HANDLE.invokeExact(); ++ } catch (final Throwable e) { ++ SneakyThrow.sneaky(e); ++ return Collections.emptySet(); // Unreachable ++ } ++ } ++ ++ private static Class findClass(final String className) throws ClassNotFoundException { ++ try { ++ return Class.forName(className); ++ } catch (final ClassNotFoundException ignore) { ++ for (final Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { ++ if (!plugin.isEnabled()) { ++ continue; ++ } ++ ++ try { ++ return Class.forName(className, false, plugin.getClass().getClassLoader()); ++ } catch (final ClassNotFoundException ignore0) { ++ } ++ } ++ } ++ throw new ClassNotFoundException(className); ++ } ++}