From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: ishland <ishlandmc@yeah.net>
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<PluginLoader> 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<PluginClassLoader> 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 79d839034d38c941745c6b91f973f908d6cdb8ee..676e2311d6f75e690c0b814c253d91a9d00e4da7 100644
--- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
@@ -233,4 +233,13 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot
                    '}';
     }
     // Paper end
+
+    // Yatopia start - Accessor
+    public java.util.Collection<Class<?>> 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<Map<Plugin, Set<Class<?>>>> loadedClassesSupplier = Suppliers.memoizeWithExpiration(StackTraceUtils::scanForPluginClasses, 5, TimeUnit.SECONDS);
+
+    public static void print(StackTraceElement[] stackTrace, Consumer<String> out) {
+        Set<Plugin> suspectedPlugins = getSuspectedPluginsFromStackTrace(stackTrace);
+
+        printSuspectedPlugins(out, suspectedPlugins);
+    }
+
+    public static void print(Throwable t, Consumer<String> out) {
+        Set<Plugin> suspectedPlugins = getSuspectedPluginsFromStackTrace(getStackTracesFromThrowable(t).toArray(new StackTraceElement[0]));
+
+        printSuspectedPlugins(out, suspectedPlugins);
+    }
+
+    private static Set<StackTraceElement> getStackTracesFromThrowable(Throwable t) {
+        if(t == null) return Collections.emptySet();
+        Set<StackTraceElement> 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<String> out, Set<Plugin> 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<Plugin> getSuspectedPluginsFromStackTrace(StackTraceElement[] stackTrace) {
+        Map<Plugin, Set<Class<?>>> loadedClasses = loadedClassesSupplier.get();
+        Set<Plugin> suspectedPlugins = new HashSet<>();
+        for (StackTraceElement stackTraceElement : stackTrace) {
+            for (Map.Entry<Plugin, Set<Class<?>>> pluginSetEntry : loadedClasses.entrySet()) {
+                if (pluginSetEntry.getValue().stream().anyMatch(clazz -> clazz.getName().equals(stackTraceElement.getClassName())))
+                    suspectedPlugins.add(pluginSetEntry.getKey());
+            }
+        }
+        return suspectedPlugins;
+    }
+
+    private static Map<Plugin, Set<Class<?>>> scanForPluginClasses() {
+        Map<Plugin, Set<Class<?>>> loadedClasses = new Object2ObjectOpenHashMap<>();
+        if (Bukkit.getPluginManager() instanceof SimplePluginManager) {
+            final SimplePluginManager pluginManager = (SimplePluginManager) Bukkit.getPluginManager();
+            final Collection<PluginLoader> pluginLoaders = pluginManager.getPluginLoaders();
+            for (PluginLoader pluginLoader : pluginLoaders) {
+                if (pluginLoader instanceof JavaPluginLoader) {
+                    JavaPluginLoader javaPluginLoader = (JavaPluginLoader) pluginLoader;
+                    final List<PluginClassLoader> 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 19271057cf24329757c9419fa6c97848e008a96c..a61414013281f136368590d1b9d3da6d41255791 100644
--- a/src/test/java/org/bukkit/AnnotationTest.java
+++ b/src/test/java/org/bukkit/AnnotationTest.java
@@ -55,8 +55,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
