diff --git a/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch index f291323de9..0c9f7d2d48 100644 --- a/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch @@ -55,7 +55,7 @@ } } -@@ -347,6 +350,30 @@ +@@ -347,6 +350,38 @@ File file5 = new File(file, fileName + ".dat"); File file6 = new File(playerDataFolder, uuid + ".dat"); @@ -65,7 +65,11 @@ + try { + root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap()); + } catch (Exception exception) { -+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper ++ // Paper start ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); ++ exception.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); ++ // Paper end + } + + if (root != null) { @@ -78,7 +82,11 @@ + try { + NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); + } catch (Exception exception) { -+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper ++ // Paper start ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); ++ exception.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); ++ // Paper end + } + } + // CraftBukkit end diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch index 87c2f6b5e5..4b4a523928 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch @@ -1,6 +1,11 @@ --- a/net/minecraft/world/entity/ai/village/VillageSiege.java +++ b/net/minecraft/world/entity/ai/village/VillageSiege.java -@@ -121,7 +121,7 @@ +@@ -117,11 +117,12 @@ + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.EVENT, (SpawnGroupData) null); + } catch (Exception exception) { + VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + return; } entityzombie.moveTo(vec3d.x, vec3d.y, vec3d.z, world.random.nextFloat() * 360.0F, 0.0F); diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch index 581c8e8cf6..15ccb200bb 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch @@ -469,7 +469,7 @@ } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) { tickingblockentity.tick(); } -@@ -461,17 +719,18 @@ +@@ -461,17 +719,19 @@ this.tickingBlockEntities = false; gameprofilerfiller.pop(); @@ -488,12 +488,13 @@ + // Paper start - Prevent block entity and entity crashes + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); ++ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + // Paper end - Prevent block entity and entity crashes } } -@@ -510,13 +769,29 @@ +@@ -510,13 +770,29 @@ @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { @@ -524,7 +525,7 @@ this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity); } } -@@ -643,7 +918,7 @@ +@@ -643,7 +919,7 @@ for (int k = 0; k < j; ++k) { EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; @@ -533,7 +534,7 @@ if (t0 != null && predicate.test(t0)) { result.add(t0); -@@ -912,7 +1187,7 @@ +@@ -912,7 +1188,7 @@ public static enum ExplosionInteraction implements StringRepresentable { diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch index f26460ed82..18d4ed97cf 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch @@ -68,7 +68,23 @@ if (j >= entityinsentient.getMaxSpawnClusterSize()) { return; } -@@ -369,7 +395,7 @@ +@@ -268,6 +294,7 @@ + NaturalSpawner.LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(type)); + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + } + + return null; +@@ -356,6 +383,7 @@ + entity = biomesettingsmobs_c.type.create(world.getLevel(), EntitySpawnReason.NATURAL); + } catch (Exception exception) { + NaturalSpawner.LOGGER.warn("Failed to create mob", exception); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + continue; + } + +@@ -369,7 +397,7 @@ if (entityinsentient.checkSpawnRules(world, EntitySpawnReason.CHUNK_GENERATION) && entityinsentient.checkSpawnObstruction(world)) { groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupdataentity); @@ -77,7 +93,7 @@ flag = true; } } -@@ -482,10 +508,12 @@ +@@ -482,10 +510,12 @@ return this.unmodifiableMobCategoryCounts; } diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch index 52e05f8c93..6240bb6d11 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -217,15 +217,22 @@ if (tileentity == null) { CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos); -@@ -447,6 +502,7 @@ +@@ -446,7 +501,13 @@ + BlockState iblockdata = this.getBlockState(blockposition); if (!iblockdata.hasBlockEntity()) { - LevelChunk.LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", new Object[]{blockEntity, blockposition, iblockdata}); -+ new Exception().printStackTrace(); // CraftBukkit +- LevelChunk.LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", new Object[]{blockEntity, blockposition, iblockdata}); ++ // Paper start - ServerExceptionEvent ++ com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException( ++ "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockposition, iblockdata) ++ ); ++ e.printStackTrace(); ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e); ++ // Paper end - ServerExceptionEvent } else { BlockState iblockdata1 = blockEntity.getBlockState(); -@@ -500,6 +556,12 @@ +@@ -500,6 +561,12 @@ if (this.isInLevel()) { BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos); @@ -238,10 +245,14 @@ if (tileentity != null) { Level world = this.level; -@@ -553,6 +615,65 @@ - - } - +@@ -549,9 +616,68 @@ + if (this.postLoad != null) { + this.postLoad.run(this); + this.postLoad = null; ++ } ++ ++ } ++ + // CraftBukkit start + public void loadCallback() { + // Paper start @@ -279,9 +290,9 @@ + } + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); + } -+ } + } + } -+ + + public void unloadCallback() { + org.bukkit.Server server = this.level.getCraftServer(); + org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); @@ -298,13 +309,12 @@ + @Override + public boolean isUnsaved() { + return super.isUnsaved() && !this.mustNotSave; -+ } + } + // CraftBukkit end -+ + public boolean isEmpty() { return false; - } -@@ -750,7 +871,7 @@ +@@ -750,7 +876,7 @@ private void updateBlockEntityTicker(T blockEntity) { BlockState iblockdata = blockEntity.getBlockState(); @@ -313,7 +323,7 @@ if (blockentityticker == null) { this.removeBlockEntityTicker(blockEntity.getBlockPos()); -@@ -841,7 +962,7 @@ +@@ -841,7 +967,7 @@ private boolean loggedInvalidBlockState; BoundTickingBlockEntity(final BlockEntity tileentity, final BlockEntityTicker blockentityticker) { @@ -322,7 +332,7 @@ this.ticker = blockentityticker; } -@@ -867,11 +988,12 @@ +@@ -867,11 +993,13 @@ gameprofilerfiller.pop(); } catch (Throwable throwable) { @@ -334,6 +344,7 @@ + // Paper start - Prevent block entity and entity crashes + final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); ++ net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent + LevelChunk.this.removeBlockEntity(this.getPos()); + // Paper end - Prevent block entity and entity crashes + // Spigot start diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch index 601f408a87..22f16d1b64 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch @@ -69,7 +69,15 @@ if (bytebuffer.remaining() != 5) { return false; } else { -@@ -349,7 +365,7 @@ +@@ -280,6 +296,7 @@ + return true; + } + } catch (IOException ioexception) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper - ServerExceptionEvent + return false; + } + } +@@ -349,7 +366,7 @@ bytebuffer.putInt(1); bytebuffer.put((byte) (this.version.getId() | 128)); @@ -78,7 +86,7 @@ return bytebuffer; } -@@ -358,7 +374,7 @@ +@@ -358,9 +375,10 @@ FileChannel filechannel = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.WRITE); try { @@ -86,8 +94,11 @@ + ((java.nio.Buffer) buf).position(5); // CraftBukkit - decompile error filechannel.write(buf); } catch (Throwable throwable) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper - ServerExceptionEvent if (filechannel != null) { -@@ -382,7 +398,7 @@ + try { + filechannel.close(); +@@ -382,7 +400,7 @@ } private void writeHeader() throws IOException { @@ -96,7 +107,7 @@ this.file.write(this.header, 0L); } -@@ -418,7 +434,7 @@ +@@ -418,7 +436,7 @@ if (i != j) { ByteBuffer bytebuffer = RegionFile.PADDING_BUFFER.duplicate(); diff --git a/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java b/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java new file mode 100644 index 0000000000..f699ce18ca --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/ServerSchedulerReportingWrapper.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper; + +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.scheduler.CraftTask; +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.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; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 0385aa1e5c..152c816efb 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -415,20 +415,25 @@ public class CraftScheduler implements BukkitScheduler { try { task.run(); } catch (final Throwable throwable) { + // Paper start + final String logMessage = 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()), + logMessage, throwable); + org.bukkit.Bukkit.getServer().getPluginManager().callEvent( + new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerSchedulerException(logMessage, throwable, task))); + // Paper end } finally { this.currentTask = null; } this.parsePending(); } else { this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); - this.executor.execute(task); + this.executor.execute(new com.destroystokyo.paper.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) }