diff --git a/Spigot-API-Patches/0021-Add-exception-reporting-event.patch b/Spigot-API-Patches/0021-Add-exception-reporting-event.patch new file mode 100644 index 0000000000..95562c1de2 --- /dev/null +++ b/Spigot-API-Patches/0021-Add-exception-reporting-event.patch @@ -0,0 +1,615 @@ +From 19bf0a56a0ee703d978da8e5f84999d1f5b20602 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Sat, 20 Feb 2016 20:07:46 -0500 +Subject: [PATCH] Add exception reporting event + + +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index 12d9232..f35bc09 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -17,6 +17,9 @@ import org.bukkit.Server; + import org.bukkit.command.defaults.*; + import org.bukkit.entity.Player; + import org.bukkit.util.StringUtil; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerCommandException; ++import org.github.paperspigot.exception.ServerTabCompleteException; + + public class SimpleCommandMap implements CommandMap { + private static final Pattern PATTERN_ON_SPACE = Pattern.compile(" ", Pattern.LITERAL); +@@ -147,7 +150,9 @@ public class SimpleCommandMap implements CommandMap { + throw ex; + } catch (Throwable ex) { + target.timings.stopTiming(); // Spigot +- throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex); ++ String msg = "Unhandled exception executing '" + commandLine + "' in " + target; ++ server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerCommandException(ex, target, sender, args))); // Paper ++ throw new CommandException(msg, ex); + } + + // return true as command was handled +@@ -224,7 +229,9 @@ public class SimpleCommandMap implements CommandMap { + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { +- throw new CommandException("Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target, ex); ++ String msg = "Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target; ++ server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerTabCompleteException(msg, ex, target, sender, args))); // Paper ++ throw new CommandException(msg, ex); + } + } + // PaperSpigot end +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 1325b03..ce9839e 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -33,6 +33,9 @@ import org.bukkit.permissions.PermissionDefault; + import org.bukkit.util.FileUtil; + + import com.google.common.collect.ImmutableSet; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerEventException; ++import org.github.paperspigot.exception.ServerPluginEnableDisableException; + + /** + * Handles all plugin management from the Server +@@ -403,7 +406,8 @@ public final class SimplePluginManager implements PluginManager { + try { + plugin.getPluginLoader().enablePlugin(plugin); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while enabling " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); + } + + HandlerList.bakeAll(); +@@ -422,36 +426,48 @@ public final class SimplePluginManager implements PluginManager { + try { + plugin.getPluginLoader().disablePlugin(plugin); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while disabling " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getScheduler().cancelTasks(plugin); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while cancelling tasks for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering services for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while unregistering services for " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + HandlerList.unregisterAll(plugin); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering events for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while unregistering events for " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getMessenger().unregisterIncomingPluginChannel(plugin); + server.getMessenger().unregisterOutgoingPluginChannel(plugin); + } catch(Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering plugin channels for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for " ++ + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + } + } + ++ // Paper start ++ private void handlePluginException(String msg, Throwable ex, Plugin plugin) { ++ server.getLogger().log(Level.SEVERE, msg, ex); ++ callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); ++ } ++ // Paper end ++ + public void clearPlugins() { + synchronized (this) { + disablePlugins(); +@@ -513,7 +529,13 @@ public final class SimplePluginManager implements PluginManager { + )); + } + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(), 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); ++ 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))); ++ } ++ // Paper end + } + } + } +diff --git a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java +index 4c171e8..9ef6525 100644 +--- a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java ++++ b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java +@@ -8,6 +8,8 @@ import java.util.Map; + import java.util.Set; + import org.bukkit.entity.Player; + import org.bukkit.plugin.Plugin; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerPluginMessageException; + + /** + * Standard implementation to {@link Messenger} +@@ -427,7 +429,13 @@ public class StandardMessenger implements Messenger { + registration.getListener().onPluginMessageReceived( channel, source, message ); + } catch ( Throwable t ) + { +- org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Could not pass incoming plugin message to " + registration.getPlugin(), t ); ++ // Paper start ++ String msg = "Could not pass incoming plugin message to " + registration.getPlugin(); ++ org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, msg, t ); ++ source.getServer().getPluginManager().callEvent(new ServerExceptionEvent( ++ new ServerPluginMessageException(msg, t, registration.getPlugin(), source, channel, message) ++ )); ++ // Paper end + } + // Spigot End + } +diff --git a/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java b/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java +new file mode 100644 +index 0000000..317e9d2 +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/event/ServerExceptionEvent.java +@@ -0,0 +1,36 @@ ++package org.github.paperspigot.event; ++ ++import com.google.common.base.Preconditions; ++import org.apache.commons.lang.Validate; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.github.paperspigot.exception.ServerException; ++ ++/** ++ * Called whenever an exception is thrown in a recoverable section of the server. ++ */ ++public class ServerExceptionEvent extends Event { ++ private static final HandlerList handlers = new HandlerList(); ++ private ServerException exception; ++ ++ public ServerExceptionEvent (ServerException exception) { ++ this.exception = Preconditions.checkNotNull(exception, "exception"); ++ } ++ ++ /** ++ * Gets the wrapped exception that was thrown. ++ * @return Exception thrown ++ */ ++ public ServerException getException() { ++ return exception; ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerCommandException.java b/src/main/java/org/github/paperspigot/exception/ServerCommandException.java +new file mode 100644 +index 0000000..aae647a +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerCommandException.java +@@ -0,0 +1,64 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Thrown when a command throws an exception ++ */ ++public class ServerCommandException extends ServerException { ++ ++ private final Command command; ++ private final CommandSender commandSender; ++ private final String[] arguments; ++ ++ public ServerCommandException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ public ServerCommandException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(cause); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ protected ServerCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ /** ++ * Gets the command which threw the exception ++ * ++ * @return exception throwing command ++ */ ++ public Command getCommand() { ++ return command; ++ } ++ ++ /** ++ * Gets the command sender which executed the command request ++ * ++ * @return command sender of exception thrown command request ++ */ ++ public CommandSender getCommandSender() { ++ return commandSender; ++ } ++ ++ /** ++ * Gets the arguments which threw the exception for the command ++ * ++ * @return arguments of exception thrown command request ++ */ ++ public String[] getArguments() { ++ return arguments; ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerEventException.java b/src/main/java/org/github/paperspigot/exception/ServerEventException.java +new file mode 100644 +index 0000000..d99cab8 +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerEventException.java +@@ -0,0 +1,52 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.event.Event; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.Plugin; ++ ++import static com.google.common.base.Preconditions.*; ++ ++/** ++ * Exception thrown when a server event listener throws an exception ++ */ ++public class ServerEventException extends ServerPluginException { ++ ++ private final Listener listener; ++ private final Event event; ++ ++ public ServerEventException(String message, Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { ++ super(message, cause, responsiblePlugin); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ public ServerEventException(Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { ++ super(cause, responsiblePlugin); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ protected ServerEventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Listener listener, Event event) { ++ super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ /** ++ * Gets the listener which threw the exception ++ * ++ * @return event listener ++ */ ++ public Listener getListener() { ++ return listener; ++ } ++ ++ /** ++ * Gets the event which caused the exception ++ * ++ * @return event ++ */ ++ public Event getEvent() { ++ return event; ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerException.java b/src/main/java/org/github/paperspigot/exception/ServerException.java +new file mode 100644 +index 0000000..f7aad05 +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerException.java +@@ -0,0 +1,23 @@ ++package org.github.paperspigot.exception; ++ ++/** ++ * Wrapper exception for all exceptions that are thrown by the server. ++ */ ++public class ServerException extends Exception { ++ ++ public ServerException(String message) { ++ super(message); ++ } ++ ++ public ServerException(String message, Throwable cause) { ++ super(message, cause); ++ } ++ ++ public ServerException(Throwable cause) { ++ super(cause); ++ } ++ ++ protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerInternalException.java b/src/main/java/org/github/paperspigot/exception/ServerInternalException.java +new file mode 100644 +index 0000000..17ad4cb +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerInternalException.java +@@ -0,0 +1,35 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.entity.ThrownExpBottle; ++import org.github.paperspigot.event.ServerExceptionEvent; ++ ++/** ++ * Thrown when the internal server throws a recoverable exception. ++ */ ++public class ServerInternalException extends ServerException { ++ ++ public ServerInternalException(String message) { ++ super(message); ++ } ++ ++ public ServerInternalException(String message, Throwable cause) { ++ super(message, cause); ++ } ++ ++ public ServerInternalException(Throwable cause) { ++ super(cause); ++ } ++ ++ protected ServerInternalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ } ++ ++ public static void reportInternalException(Throwable cause) { ++ try { ++ Bukkit.getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(cause)));; ++ } catch (Throwable t) { ++ t.printStackTrace(); // Don't want to rethrow! ++ } ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java b/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java +new file mode 100644 +index 0000000..8c938d0 +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerPluginEnableDisableException.java +@@ -0,0 +1,20 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.plugin.Plugin; ++ ++/** ++ * Thrown whenever there is an exception with any enabling or disabling of plugins. ++ */ ++public class ServerPluginEnableDisableException extends ServerPluginException { ++ public ServerPluginEnableDisableException(String message, Throwable cause, Plugin responsiblePlugin) { ++ super(message, cause, responsiblePlugin); ++ } ++ ++ public ServerPluginEnableDisableException(Throwable cause, Plugin responsiblePlugin) { ++ super(cause, responsiblePlugin); ++ } ++ ++ protected ServerPluginEnableDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { ++ super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerPluginException.java b/src/main/java/org/github/paperspigot/exception/ServerPluginException.java +new file mode 100644 +index 0000000..f9241ad +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerPluginException.java +@@ -0,0 +1,39 @@ ++package org.github.paperspigot.exception; ++ ++import com.google.common.base.Preconditions; ++import org.apache.commons.lang.Validate; ++import org.bukkit.plugin.Plugin; ++ ++import static com.google.common.base.Preconditions.*; ++ ++/** ++ * Wrapper exception for all cases to which a plugin can be immediately blamed for ++ */ ++public class ServerPluginException extends ServerException { ++ public ServerPluginException(String message, Throwable cause, Plugin responsiblePlugin) { ++ super(message, cause); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ public ServerPluginException(Throwable cause, Plugin responsiblePlugin) { ++ super(cause); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ protected ServerPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ private final Plugin responsiblePlugin; ++ ++ /** ++ * Gets the plugin which is directly responsible for the exception being thrown ++ * ++ * @return plugin which is responsible for the exception throw ++ */ ++ public Plugin getResponsiblePlugin() { ++ return responsiblePlugin; ++ } ++ ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java b/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java +new file mode 100644 +index 0000000..b71303b +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerPluginMessageException.java +@@ -0,0 +1,61 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.entity.Player; ++import org.bukkit.plugin.Plugin; ++ ++import static com.google.common.base.Preconditions.*; ++ ++/** ++ * Thrown when an incoming plugin message channel throws an exception ++ */ ++public class ServerPluginMessageException extends ServerPluginException { ++ ++ private final Player player; ++ private final String channel; ++ private final byte[] data; ++ ++ public ServerPluginMessageException(String message, Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { ++ super(message, cause, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ public ServerPluginMessageException(Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { ++ super(cause, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ protected ServerPluginMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Player player, String channel, byte[] data) { ++ super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ /** ++ * Gets the channel to which the error occurred from recieving data from ++ * @return exception channel ++ */ ++ public String getChannel() { ++ return channel; ++ } ++ ++ /** ++ * Gets the data to which the error occurred from ++ * @return exception data ++ */ ++ public byte[] getData() { ++ return data; ++ } ++ ++ /** ++ * Gets the player which the plugin message causing the exception originated from ++ * @return exception player ++ */ ++ public Player getPlayer() { ++ return player; ++ } ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java b/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java +new file mode 100644 +index 0000000..99757ee +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerSchedulerException.java +@@ -0,0 +1,37 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.scheduler.BukkitTask; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Thrown when a plugin's scheduler fails with an exception ++ */ ++public class ServerSchedulerException extends ServerPluginException { ++ ++ private final BukkitTask task; ++ ++ public ServerSchedulerException(String message, Throwable cause, BukkitTask task) { ++ super(message, cause, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ public ServerSchedulerException(Throwable cause, BukkitTask task) { ++ super(cause, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ protected ServerSchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BukkitTask task) { ++ super(message, cause, enableSuppression, writableStackTrace, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ /** ++ * Gets the task which threw the exception ++ * @return exception throwing task ++ */ ++ public BukkitTask getTask() { ++ return task; ++ } ++ ++} +diff --git a/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java b/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java +new file mode 100644 +index 0000000..6e1e2ab +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/exception/ServerTabCompleteException.java +@@ -0,0 +1,22 @@ ++package org.github.paperspigot.exception; ++ ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++/** ++ * Called when a tab-complete request throws an exception ++ */ ++public class ServerTabCompleteException extends ServerCommandException { ++ ++ public ServerTabCompleteException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, command, commandSender, arguments); ++ } ++ ++ public ServerTabCompleteException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(cause, command, commandSender, arguments); ++ } ++ ++ protected ServerTabCompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, enableSuppression, writableStackTrace, command, commandSender, arguments); ++ } ++} +-- +2.5.0 + diff --git a/Spigot-Server-Patches/0100-Add-exception-reporting-event.patch b/Spigot-Server-Patches/0100-Add-exception-reporting-event.patch new file mode 100644 index 0000000000..e75377a013 --- /dev/null +++ b/Spigot-Server-Patches/0100-Add-exception-reporting-event.patch @@ -0,0 +1,466 @@ +From 3222d65f280871d3cc7b9a3d0250934dcc5f456c Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Sat, 20 Feb 2016 20:07:46 -0500 +Subject: [PATCH] Add exception reporting event + + +diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java +index aa555f5..ab0d41c 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -18,6 +18,9 @@ import org.apache.logging.log4j.Logger; + + import com.google.common.collect.Lists; // CraftBukkit + import org.bukkit.Bukkit; // CraftBukkit ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerInternalException; + + public class Chunk { + +@@ -869,10 +872,15 @@ public class Chunk { + this.tileEntities.remove(blockposition); + // PaperSpigot end + } else { +- System.out.println("Attempted to place a tile entity (" + tileentity + ") at " + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ() +- + " (" + org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(getType(blockposition)) + ") where there was no entity tile!"); +- System.out.println("Chunk coordinates: " + (this.locX * 16) + "," + (this.locZ * 16)); +- new Exception().printStackTrace(); ++ // Paper start ++ ServerInternalException e = new ServerInternalException( ++ "Attempted to place a tile entity (" + tileentity + ") at " + tileentity.position.getX() + "," ++ + tileentity.position.getY() + "," + tileentity.position.getZ() ++ + " (" + CraftMagicNumbers.getMaterial(getType(blockposition)) + ") where there was no entity tile!\n" + ++ "Chunk coordinates: " + (this.locX * 16) + "," + (this.locZ * 16)); ++ e.printStackTrace(); ++ ServerInternalException.reportInternalException(e); ++ // Paper end + // CraftBukkit end + } + } +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 0e6a37f..c4dab42 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -15,12 +15,15 @@ import org.apache.logging.log4j.Logger; + import java.util.Random; + import java.util.logging.Level; + ++import org.bukkit.Bukkit; + import org.bukkit.Server; + import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor; + import org.bukkit.craftbukkit.util.LongHash; + import org.bukkit.craftbukkit.util.LongHashSet; + import org.bukkit.craftbukkit.util.LongObjectHashMap; + import org.bukkit.event.world.ChunkUnloadEvent; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerInternalException; + // CraftBukkit end + + public class ChunkProviderServer implements IChunkProvider { +@@ -215,11 +218,14 @@ public class ChunkProviderServer implements IChunkProvider { + + if (chunk == emptyChunk) return chunk; + if (i != chunk.locX || j != chunk.locZ) { +- b.error("Chunk (" + chunk.locX + ", " + chunk.locZ + ") stored at (" + i + ", " + j + ") in world '" + world.getWorld().getName() + "'"); ++ // Paper start ++ String msg = "Chunk (" + chunk.locX + ", " + chunk.locZ + ") stored at (" + i + ", " + j + ") in world '" + world.getWorld().getName() + "'"; ++ b.error(msg); + b.error(chunk.getClass().getName()); +- Throwable ex = new Throwable(); +- ex.fillInStackTrace(); ++ ServerInternalException ex = new ServerInternalException(msg); + ex.printStackTrace(); ++ Bukkit.getPluginManager().callEvent(new ServerExceptionEvent(ex)); ++ // Paper end + } + + return chunk; +@@ -244,7 +250,11 @@ public class ChunkProviderServer implements IChunkProvider { + + return chunk; + } catch (Exception exception) { +- ChunkProviderServer.b.error("Couldn\'t load chunk", exception); ++ // Paper start ++ String msg = "Couldn\'t load chunk"; ++ ChunkProviderServer.b.error(msg, exception); ++ ServerInternalException.reportInternalException(exception); ++ // Paper end + return null; + } + } +diff --git a/src/main/java/net/minecraft/server/NameReferencingFileConverter.java b/src/main/java/net/minecraft/server/NameReferencingFileConverter.java +index d67539c..4029109 100644 +--- a/src/main/java/net/minecraft/server/NameReferencingFileConverter.java ++++ b/src/main/java/net/minecraft/server/NameReferencingFileConverter.java +@@ -25,6 +25,9 @@ import java.util.Map; + import java.util.UUID; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.bukkit.Bukkit; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerInternalException; + + public class NameReferencingFileConverter { + +@@ -361,6 +364,7 @@ public class NameReferencingFileConverter { + root = NBTCompressedStreamTools.a(new java.io.FileInputStream(file1)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + if (root != null) { +@@ -374,8 +378,9 @@ public class NameReferencingFileConverter { + NBTCompressedStreamTools.a(root, new java.io.FileOutputStream(file2)); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } +- } ++ } + // CraftBukkit end + + NameReferencingFileConverter.b(file); +diff --git a/src/main/java/net/minecraft/server/PersistentCollection.java b/src/main/java/net/minecraft/server/PersistentCollection.java +index 451f481..6459594 100644 +--- a/src/main/java/net/minecraft/server/PersistentCollection.java ++++ b/src/main/java/net/minecraft/server/PersistentCollection.java +@@ -2,6 +2,10 @@ package net.minecraft.server; + + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; ++import org.bukkit.Bukkit; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerInternalException; ++ + import java.io.DataInputStream; + import java.io.DataOutput; + import java.io.DataOutputStream; +@@ -103,6 +107,7 @@ public class PersistentCollection { + } + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); + } + + } +@@ -176,6 +181,7 @@ public class PersistentCollection { + } + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + return oshort.shortValue(); +diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java +index 348706f..8165a50 100644 +--- a/src/main/java/net/minecraft/server/RegionFile.java ++++ b/src/main/java/net/minecraft/server/RegionFile.java +@@ -1,6 +1,8 @@ + package net.minecraft.server; + + import com.google.common.collect.Lists; ++import org.github.paperspigot.exception.ServerInternalException; ++ + import java.io.BufferedInputStream; + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; +@@ -82,6 +84,7 @@ public class RegionFile { + } + } catch (IOException ioexception) { + ioexception.printStackTrace(); ++ ServerInternalException.reportInternalException(ioexception); // Paper + } + + } +@@ -247,6 +250,7 @@ public class RegionFile { + this.b(i, j, (int) (MinecraftServer.az() / 1000L)); + } catch (IOException ioexception) { + ioexception.printStackTrace(); ++ ServerInternalException.reportInternalException(ioexception); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java +index 5b000c4..4cfaf30 100644 +--- a/src/main/java/net/minecraft/server/RegionFileCache.java ++++ b/src/main/java/net/minecraft/server/RegionFileCache.java +@@ -1,6 +1,8 @@ + package net.minecraft.server; + + import com.google.common.collect.Maps; ++import org.github.paperspigot.exception.ServerInternalException; ++ + import java.io.DataInputStream; + import java.io.DataOutputStream; + import java.io.File; +@@ -53,6 +55,7 @@ public class RegionFileCache { + } + } catch (IOException ioexception) { + ioexception.printStackTrace(); ++ ServerInternalException.reportInternalException(ioexception); // Paper + } + } + +diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java +index 611b8fd..284f4bf 100644 +--- a/src/main/java/net/minecraft/server/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/server/SpawnerCreature.java +@@ -10,6 +10,7 @@ import java.util.Set; + import org.bukkit.craftbukkit.util.LongHash; + import org.bukkit.craftbukkit.util.LongHashSet; + import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; ++import org.github.paperspigot.exception.ServerInternalException; + // CraftBukkit end + + public final class SpawnerCreature { +@@ -171,6 +172,7 @@ public final class SpawnerCreature { + entityinsentient = (EntityInsentient) biomebase_biomemeta.b.getConstructor(new Class[] { World.class}).newInstance(new Object[] { worldserver}); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + return j1; + } + +@@ -276,6 +278,7 @@ public final class SpawnerCreature { + entityinsentient = (EntityInsentient) biomebase_biomemeta.b.getConstructor(new Class[] { World.class}).newInstance(new Object[] { world}); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + continue; + } + +diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java +index fdcbf44..f68b132 100644 +--- a/src/main/java/net/minecraft/server/TileEntity.java ++++ b/src/main/java/net/minecraft/server/TileEntity.java +@@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger; + import co.aikar.timings.SpigotTimings; // Spigot + import co.aikar.timings.Timing; // Spigot + import org.bukkit.inventory.InventoryHolder; // CraftBukkit ++import org.github.paperspigot.exception.ServerInternalException; + + public abstract class TileEntity { + +@@ -76,6 +77,7 @@ public abstract class TileEntity { + } + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + if (tileentity != null) { +diff --git a/src/main/java/net/minecraft/server/VillageSiege.java b/src/main/java/net/minecraft/server/VillageSiege.java +index 42d6737..01dbc98 100644 +--- a/src/main/java/net/minecraft/server/VillageSiege.java ++++ b/src/main/java/net/minecraft/server/VillageSiege.java +@@ -1,5 +1,7 @@ + package net.minecraft.server; + ++import org.github.paperspigot.exception.ServerInternalException; ++ + import java.util.Iterator; + import java.util.List; + +@@ -136,6 +138,7 @@ public class VillageSiege { + entityzombie.setVillager(false); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index f0cd810..8f3511f 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -24,6 +24,8 @@ import java.util.concurrent.Callable; + import java.util.concurrent.ExecutorService; + import java.util.concurrent.Executors; + import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerInternalException; + // PaperSpigot end + + // CraftBukkit start +@@ -1450,8 +1452,10 @@ public abstract class World implements IBlockAccess { + } catch (Throwable throwable1) { + // PaperSpigot start - Prevent tile entity and entity crashes + entity.tickTimer.stopTiming(); +- System.err.println("Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX + "," + entity.locY + "," + entity.locZ); ++ String msg = "Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX + "," + entity.locY + "," + entity.locZ; ++ System.err.println(msg); + throwable1.printStackTrace(); ++ getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable1))); + entity.dead = true; + continue; + // PaperSpigot end +@@ -1513,8 +1517,10 @@ public abstract class World implements IBlockAccess { + } catch (Throwable throwable2) { + // PaperSpigot start - Prevent tile entity and entity crashes + tileentity.tickTimer.stopTiming(); +- System.err.println("TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()); ++ String msg = "TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ(); ++ System.err.println(msg); + throwable2.printStackTrace(); ++ getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable2))); + tilesThisCycle--; + this.tileEntityList.remove(tileTickPosition--); + continue; +diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java +index e5124af..b4056a2 100644 +--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java +@@ -15,6 +15,7 @@ import org.apache.logging.log4j.Logger; + import java.util.UUID; + + import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.github.paperspigot.exception.ServerInternalException; + // CraftBukkit end + + public class WorldNBTStorage implements IDataManager, IPlayerFileData { +@@ -96,6 +97,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + return new WorldData(nbttagcompound1); + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + } + +@@ -107,6 +109,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + return new WorldData(nbttagcompound1); + } catch (Exception exception1) { + exception1.printStackTrace(); ++ ServerInternalException.reportInternalException(exception1); // Paper + } + } + +@@ -140,6 +143,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + } + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + } +@@ -171,6 +175,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + } + } catch (Exception exception) { + exception.printStackTrace(); ++ ServerInternalException.reportInternalException(exception); // Paper + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 72c0b17..7640070 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -55,6 +55,7 @@ import org.bukkit.metadata.MetadataValue; + import org.bukkit.plugin.Plugin; + import org.bukkit.plugin.messaging.StandardMessenger; + import org.bukkit.util.Vector; ++import org.github.paperspigot.exception.ServerInternalException; + + public class CraftWorld implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; +@@ -778,6 +779,7 @@ public class CraftWorld implements World { + world.savingDisabled = oldSave; + } catch (ExceptionWorldConflict ex) { + ex.printStackTrace(); ++ ServerInternalException.reportInternalException(ex); // Paper + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index d76ec40..f036709 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -21,6 +21,9 @@ import org.bukkit.scheduler.BukkitRunnable; + import org.bukkit.scheduler.BukkitScheduler; + import org.bukkit.scheduler.BukkitTask; + import org.bukkit.scheduler.BukkitWorker; ++import org.github.paperspigot.ServerSchedulerReportingWrapper; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerSchedulerException; + + /** + * The fundamental concepts for this implementation: +@@ -348,18 +351,24 @@ public class CraftScheduler implements BukkitScheduler { + try { + task.run(); + } catch (final Throwable throwable) { ++ // Paper start ++ String msg = String.format( ++ "Task #%s for %s generated an exception", ++ task.getTaskId(), ++ task.getOwner().getDescription().getFullName()); + task.getOwner().getLogger().log( + Level.WARNING, +- String.format( +- "Task #%s for %s generated an exception", +- task.getTaskId(), +- task.getOwner().getDescription().getFullName()), ++ msg, + throwable); ++ task.getOwner().getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task)) ++ ); ++ // Paper end + } + parsePending(); + } else { + debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); +- executor.execute(task); ++ executor.execute(new ServerSchedulerReportingWrapper(task)); // Paper + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } +diff --git a/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java b/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java +new file mode 100644 +index 0000000..6499373 +--- /dev/null ++++ b/src/main/java/org/github/paperspigot/ServerSchedulerReportingWrapper.java +@@ -0,0 +1,39 @@ ++package org.github.paperspigot; ++ ++import com.google.common.base.Preconditions; ++import org.apache.commons.lang.Validate; ++import org.bukkit.craftbukkit.scheduler.CraftTask; ++import org.github.paperspigot.event.ServerExceptionEvent; ++import org.github.paperspigot.exception.ServerSchedulerException; ++ ++/** ++ * Reporting wrapper to catch exceptions not natively ++ */ ++public class ServerSchedulerReportingWrapper implements Runnable { ++ ++ private final CraftTask internalTask; ++ ++ public ServerSchedulerReportingWrapper(CraftTask internalTask) { ++ this.internalTask = Preconditions.checkNotNull(internalTask, "internalTask"); ++ } ++ ++ @Override ++ public void run() { ++ try { ++ internalTask.run(); ++ } catch (RuntimeException e) { ++ internalTask.getOwner().getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(e, internalTask)) ++ ); ++ throw e; ++ } catch (Throwable t) { ++ internalTask.getOwner().getServer().getPluginManager().callEvent( ++ new ServerExceptionEvent(new ServerSchedulerException(t, internalTask)) ++ ); //Do not rethrow, since it is not permitted with Runnable#run ++ } ++ } ++ ++ public CraftTask getInternalTask() { ++ return internalTask; ++ } ++} +-- +2.5.0 +