PaperMC / Paper

The most widely used, high performance Minecraft server that aims to fix gameplay and mechanics inconsistencies
https://papermc.io/
Other
9.34k stars 2.2k forks source link

`DragonBattle#getEnderDragon` throws internal NPE before End dimension is loaded for the first time #10970

Closed joshuaprince closed 1 week ago

joshuaprince commented 1 week ago

Only present in 1.21.

Stack trace

java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "key" is null
    at java.base/java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[?:?]
    at ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup.get(EntityLookup.java:91) ~[main/:?]
    at ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup.get(EntityLookup.java:37) ~[main/:?]
    at net.minecraft.server.level.ServerLevel.getEntity(ServerLevel.java:1826) ~[main/:?]
    at org.bukkit.craftbukkit.boss.CraftDragonBattle.getEnderDragon(CraftDragonBattle.java:32) ~[main/:?]
    at test-plugin-1.0.0-SNAPSHOT.jar/io.papermc.testplugin.TestPlugin.onBreak(TestPlugin.java:32) ~[test-plugin-1.0.0-SNAPSHOT.jar:?]
    at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor1.execute(Unknown Source) ~[?:?]
    at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.21-R0.1-SNAPSHOT.jar:?]
    at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.21-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.21-R0.1-SNAPSHOT.jar:?]
    at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[main/:?]
    at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:131) ~[main/:?]
    at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:628) ~[paper-api-1.21-R0.1-SNAPSHOT.jar:?]
    at net.minecraft.server.level.ServerPlayerGameMode.destroyBlock(ServerPlayerGameMode.java:383) ~[main/:?]
    at net.minecraft.server.level.ServerPlayerGameMode.destroyAndAck(ServerPlayerGameMode.java:342) ~[main/:?]
    at net.minecraft.server.level.ServerPlayerGameMode.handleBlockBreakAction(ServerPlayerGameMode.java:215) ~[main/:?]
    at net.minecraft.server.network.ServerGamePacketListenerImpl.handlePlayerAction(ServerGamePacketListenerImpl.java:1785) ~[main/:?]
    at net.minecraft.network.protocol.game.ServerboundPlayerActionPacket.handle(ServerboundPlayerActionPacket.java:51) ~[filteredMinecraft.jar:?]
    at net.minecraft.network.protocol.game.ServerboundPlayerActionPacket.handle(ServerboundPlayerActionPacket.java:20) ~[filteredMinecraft.jar:?]
    at net.minecraft.network.protocol.PacketUtils.lambda$ensureRunningOnSameThread$0(PacketUtils.java:36) ~[main/:?]
    at net.minecraft.server.TickTask.run(TickTask.java:18) ~[filteredMinecraft.jar:?]
    at net.minecraft.util.thread.BlockableEventLoop.doRunTask(BlockableEventLoop.java:151) ~[main/:?]
    at net.minecraft.util.thread.ReentrantBlockableEventLoop.doRunTask(ReentrantBlockableEventLoop.java:24) ~[filteredMinecraft.jar:?]
    at net.minecraft.server.MinecraftServer.doRunTask(MinecraftServer.java:1478) ~[main/:?]
    at net.minecraft.server.MinecraftServer.doRunTask(MinecraftServer.java:201) ~[main/:?]
    at net.minecraft.util.thread.BlockableEventLoop.pollTask(BlockableEventLoop.java:125) ~[main/:?]
    at net.minecraft.server.MinecraftServer.pollTaskInternal(MinecraftServer.java:1456) ~[main/:?]
    at net.minecraft.server.MinecraftServer.pollTask(MinecraftServer.java:1449) ~[main/:?]
    at net.minecraft.util.thread.BlockableEventLoop.managedBlock(BlockableEventLoop.java:135) ~[main/:?]
    at net.minecraft.server.MinecraftServer.managedBlock(MinecraftServer.java:1408) ~[main/:?]
    at net.minecraft.server.MinecraftServer.waitUntilNextTick(MinecraftServer.java:1415) ~[main/:?]
    at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1260) ~[main/:?]
    at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:329) ~[main/:?]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]

Plugin and Datapack List

No plugins or datapacks, just the Paper TestPlugin with added logic to call DragonBattle#getEnderDragon (NB: this contains proper null checks)

    @EventHandler
    public void onBreak(BlockBreakEvent event) {
        World end = Bukkit.getWorlds().get(2);
        event.getPlayer().sendMessage(end.getName());

        DragonBattle battle = end.getEnderDragonBattle();
        event.getPlayer().sendMessage(battle == null ? "battle = null!" : battle.toString());
        if (battle == null) return;

        EnderDragon dragon = battle.getEnderDragon();  // <--- TestPlugin.java:32 mentioned in the stacktrace
        event.getPlayer().sendMessage(dragon == null ? "dragon = null!" : dragon.toString());
    }

Actions to reproduce (if known)

  1. Add the listener above to a test plugin
  2. Delete world_the_end and start the server
  3. Break a block (or otherwise trigger the code to run, BlockBreakEvent is just a hook to try this out)

Behavior in 1.20.6: No NPE, the line "dragon = null!" gets printed to the player. Behavior in 1.21: NPE.

Paper version

This server is running Paper version 1.21-DEV (2024-06-24T00:56:05Z) (Implementing API version 1.21-R0.1-SNAPSHOT)
You are running a development version without access to version information

Git SHA: dd316546 (Paper 1.21 build 36)

Other

Caught this while testing a plugin with new worlds for 1.21. The stacktrace suggests it could affect other Entity lookups, but I couldn't get any others to trigger it.

The DragonBattle object is non-null in this situation on both 1.20.6 and 1.21. 1.20.6 properly returns EnderDragon as null, 1.21 throws this exception instead.

Spottedleaf commented 1 week ago

Resolved in https://github.com/PaperMC/Paper/commit/dd49fba8c534d48c3693a751075ecb5836a9d458