From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: ishland Date: Fri, 29 Jan 2021 09:57:47 +0800 Subject: [PATCH] Suspected plugins report Added "Suspected Plugins" to Watchdog, crash reports and exception messages diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java index 26685f59b235ea5b4c4fb7ae21acb5149edaa2b3..02c20a33161094b08dc2ae9353c0504561b0b452 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -560,7 +560,11 @@ public final class SimplePluginManager implements PluginManager { // Paper start private void handlePluginException(String msg, Throwable ex, Plugin plugin) { - server.getLogger().log(Level.SEVERE, msg, ex); + // Yatopia start - detailed report + server.getLogger().log(Level.SEVERE, msg); + org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); + server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); + // Yatopia end callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); } // Paper end @@ -621,7 +625,11 @@ public final class SimplePluginManager implements PluginManager { } catch (Throwable ex) { // Paper start - error reporting String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); - server.getLogger().log(Level.SEVERE, msg, ex); + // Yatopia start - detailed report + server.getLogger().log(Level.SEVERE, msg); + org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); + server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); + // Yatopia end if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); } @@ -905,4 +913,10 @@ public final class SimplePluginManager implements PluginManager { } // Paper end + // Yatopia start - Accessor + @NotNull + public Collection getPluginLoaders() { + return new HashSet<>(fileAssociations.values()); + } + // Yatopia end } diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java index 04fa3991f6ce4e9dad804f28fc6c947695857089..cb11eab6e13ed1c395b8f7db033c9a2817f4089c 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java @@ -111,7 +111,7 @@ public abstract class JavaPlugin extends PluginBase { * @return File containing this plugin */ @NotNull - protected File getFile() { + public File getFile() { // Yatopia return file; } diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index 384edf9890dfbd1cddfdcac4db1ebe9a4d761f78..7c0c63c3f5734d59aa8b57fe3eb3c1fe0e137f12 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -379,7 +379,11 @@ public final class JavaPluginLoader implements PluginLoader { try { jPlugin.setEnabled(true); } catch (Throwable ex) { - server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + // Yatopia start - detailed report + server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)"); + org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); + server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); + // Yatopia end // Paper start - Disable plugins that fail to load server.getPluginManager().disablePlugin(jPlugin, true); // Paper - close Classloader on disable - She's dead jim return; @@ -414,7 +418,11 @@ public final class JavaPluginLoader implements PluginLoader { try { jPlugin.setEnabled(false); } catch (Throwable ex) { - server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + // Yatopia start - detailed report + server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)"); + org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); + server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); + // Yatopia end } if (cloader instanceof PluginClassLoader) { @@ -432,11 +440,20 @@ public final class JavaPluginLoader implements PluginLoader { loader.close(); } } catch (IOException e) { + // Yatopia start - detailed report server.getLogger().log(Level.WARNING, "Error closing the Plugin Class Loader for " + plugin.getDescription().getFullName()); + org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, _msg -> server.getLogger().log(Level.WARNING, _msg)); + server.getLogger().log(Level.WARNING, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, e); + // Yatopia end e.printStackTrace(); } // Paper end } } } + // Yatopia start - Accessor + public List getClassLoaders() { + return java.util.Collections.unmodifiableList(loaders); + } + // Yatopia end } diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java index 7760be3e34fa20825faf145d9fb5b2855c1a4602..2e8f3efdb683c44b3e42bb0187bc907e64cde288 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -232,4 +232,13 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot '}'; } // Paper end + + // Yatopia start - Accessor + public java.util.Collection> getLoadedClasses() { + return java.util.Collections.unmodifiableCollection( + new java.util.HashSet<>(classes.values()).stream() + .filter(clazz -> clazz.getClassLoader() == this).collect(java.util.stream.Collectors.toSet()) + ); + } + // Yatopia end } diff --git a/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java b/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0aa9bc6ad0a85d469b29201b9da29165bafb874c --- /dev/null +++ b/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java @@ -0,0 +1,105 @@ +package org.yatopiamc.yatopia.api.internal; + +import com.google.common.base.Suppliers; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.SimplePluginManager; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; +import org.bukkit.plugin.java.PluginClassLoader; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class StackTraceUtils { + + public static final String EXCEPTION_DETAILS_BELOW = "Exception details below: "; + + private static final Supplier>>> loadedClassesSupplier = Suppliers.memoizeWithExpiration(StackTraceUtils::scanForPluginClasses, 5, TimeUnit.SECONDS); + + public static void print(StackTraceElement[] stackTrace, Consumer out) { + Set suspectedPlugins = getSuspectedPluginsFromStackTrace(stackTrace); + + printSuspectedPlugins(out, suspectedPlugins); + } + + public static void print(Throwable t, Consumer out) { + Set suspectedPlugins = getSuspectedPluginsFromStackTrace(getStackTracesFromThrowable(t).toArray(new StackTraceElement[0])); + + printSuspectedPlugins(out, suspectedPlugins); + } + + private static Set getStackTracesFromThrowable(Throwable t) { + if(t == null) return Collections.emptySet(); + Set elements = new ObjectOpenHashSet<>(); + elements.addAll(getStackTracesFromThrowable(t.getCause())); + elements.addAll(Arrays.stream(t.getSuppressed()).flatMap(throwable -> getStackTracesFromThrowable(throwable).stream()).collect(Collectors.toSet())); + elements.addAll(Arrays.asList(t.getStackTrace())); + return elements; + } + + private static void printSuspectedPlugins(Consumer out, Set suspectedPlugins) { + if (!suspectedPlugins.isEmpty()) { + out.accept("Suspected Plugins: "); + for (Plugin plugin : suspectedPlugins) { + StringBuilder builder = new StringBuilder("\t"); + builder.append(plugin.getName()) + .append("{") + .append(plugin.isEnabled() ? "enabled" : "disabled") + .append(",").append("ver=").append(plugin.getDescription().getVersion()); + if (!plugin.isNaggable()) + builder.append(",").append("nag"); + if (plugin instanceof JavaPlugin) + builder.append(",").append("path=").append(((JavaPlugin) plugin).getFile()); + + builder.append("}"); + out.accept(builder.toString()); + } + } else { + out.accept("Suspected Plugins: None"); + } + } + + private static Set getSuspectedPluginsFromStackTrace(StackTraceElement[] stackTrace) { + Map>> loadedClasses = loadedClassesSupplier.get(); + Set suspectedPlugins = new HashSet<>(); + for (StackTraceElement stackTraceElement : stackTrace) { + for (Map.Entry>> pluginSetEntry : loadedClasses.entrySet()) { + if (pluginSetEntry.getValue().stream().anyMatch(clazz -> clazz.getName().equals(stackTraceElement.getClassName()))) + suspectedPlugins.add(pluginSetEntry.getKey()); + } + } + return suspectedPlugins; + } + + private static Map>> scanForPluginClasses() { + Map>> loadedClasses = new Object2ObjectOpenHashMap<>(); + if (Bukkit.getPluginManager() instanceof SimplePluginManager) { + final SimplePluginManager pluginManager = (SimplePluginManager) Bukkit.getPluginManager(); + final Collection pluginLoaders = pluginManager.getPluginLoaders(); + for (PluginLoader pluginLoader : pluginLoaders) { + if (pluginLoader instanceof JavaPluginLoader) { + JavaPluginLoader javaPluginLoader = (JavaPluginLoader) pluginLoader; + final List classLoaders = javaPluginLoader.getClassLoaders(); + for (PluginClassLoader classLoader : classLoaders) { + loadedClasses.put(classLoader.getPlugin(), new ObjectOpenHashSet<>(classLoader.getLoadedClasses())); + } + } + } + } + return loadedClasses; + } + +} diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java index 4c2780c903ec354edac741b673a7174284a9849a..ed5629295324123576af992cb1b4c7ce90ffa1de 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java +++ b/src/test/java/org/bukkit/AnnotationTest.java @@ -49,8 +49,11 @@ public class AnnotationTest { "co/aikar/timings/TimingHistory$2$1$2", "co/aikar/timings/TimingHistory$3", "co/aikar/timings/TimingHistory$4", - "co/aikar/timings/TimingHistoryEntry$1" + "co/aikar/timings/TimingHistoryEntry$1", // Paper end + // Yatopia start + "org/yatopiamc/yatopia/api/internal/StackTraceUtils" + // Yatopia end }; @Test