Skip to content

Guard Bukkit chunk unload path against reflection nulls and NPE loops - #2

Merged
larroxtv merged 2 commits into
mainfrom
copilot/fix-null-pointer-exception-in-chunk-unload
May 10, 2026
Merged

Guard Bukkit chunk unload path against reflection nulls and NPE loops#2
larroxtv merged 2 commits into
mainfrom
copilot/fix-null-pointer-exception-in-chunk-unload

Conversation

Copilot AI commented May 10, 2026

Copy link
Copy Markdown

ChunkUnloadEvent could repeatedly throw NullPointerException when AUTO_TRIM was enabled and reflection-backed chunk internals were unavailable or returned null. This caused unstable unload handling and noisy repeated failures on affected server/version combinations.

  • unloadChunk defensive null handling

    • Added early returns when required reflection members are unavailable:
      • methodGetHandleChunk
      • mustNotSave
      • methodSetUnsaved
    • Added early returns when runtime reflection results are null:
      • NMS chunk handle (c)
      • mustNotSave.of(c) field executor
  • onChunkUnload NPE containment

    • Wrapped AUTO_TRIM unload + subsequent chunk processing in an NPE guard so a single bad chunk/reflection path does not repeatedly surface exceptions on unload events.
  • Example of new guard behavior

    if (this.methodGetHandleChunk == null || this.mustNotSave == null || this.methodSetUnsaved == null) {
        return false;
    }
    Object c = ...;
    if (c == null) {
        return false;
    }
    RefField.RefExecutor field = this.mustNotSave.of(c);
    if (field == null) {
        return false;
    }

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • jitpack.io
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp (dns block)
  • maven.enginehub.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

The ChunkUnloadEvent handler in Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java throws a java.lang.NullPointerException repeatedly when chunks are unloaded.

Root cause: In the onChunkUnload method, unloadChunk(world, chunk, true) is called when Settings.Chunk_Processor.AUTO_TRIM is enabled. Inside unloadChunk, the code calls this.methodGetHandleChunk.of(chunk).call(...) and this.mustNotSave.of(c) — but this.methodGetHandleChunk and/or this.mustNotSave can be null if the reflection-based initialization in the constructor failed (the catch block sets AUTO_TRIM = false but that happens after the fields were already not set). Also, the NMS object c returned by methodGetHandleChunk can itself be null on certain server versions/configurations.

Fix needed:

  1. In unloadChunk, add null-checks for this.methodGetHandleChunk, this.mustNotSave, and the result c before proceeding. Return false early if any of these are null.
  2. Also guard the onChunkUnload method: wrap the unloadChunk call (and the subsequent processChunk) in a try-catch for NullPointerException or add proper null guards so a single problematic chunk doesn't spam the error log.

The file to change is: Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java

Copilot AI changed the title [WIP] Fix NullPointerException in ChunkUnloadEvent handler Guard Bukkit chunk unload path against reflection nulls and NPE loops May 10, 2026
Copilot AI requested a review from larroxtv May 10, 2026 21:02
@larroxtv
larroxtv marked this pull request as ready for review May 10, 2026 21:04
Copilot AI review requested due to automatic review settings May 10, 2026 21:04
@larroxtv
larroxtv merged commit e4dfb4a into main May 10, 2026
8 of 9 checks passed
Copilot AI removed the request for review from Copilot May 10, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants