Skip to content

IndexOutOfBoundsException on player join #6608

Description

@minoneer

Type of bug

Error in console

/ess dump all output

https://essentialsx.net/dump?bytebin=SoVSMwf325

Error log (if applicable)

https://pastes.dev/mRentNB1AY

Bug description

When a player joins the server, Essentials throws an IndexOutOfBoundsExceptin in AsyncPlaerSendCommandsEvent.

Steps to reproduce

  1. enable Essentials
  2. join the server

Expected behaviour

Essentials doesn't throw an exception

Actual behaviour

Essentials throws an exception

Additional Information

Versions: reproduced on EssentialsX 2.22.0 and 2.22.1-dev+17-382de4f, Paper 26.2 and 1.21.11

One stack trace per player join, via AsyncPlayerSendCommandsEvent:

[14:15:22 ERROR]: Could not pass event AsyncPlayerSendCommandsEvent to Essentials v2.22.1-dev+17-382de4f
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
        at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100) ~[?:?]
        at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106) ~[?:?]
        at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302) ~[?:?]
        at java.base/java.util.Objects.checkIndex(Objects.java:365) ~[?:?]
        at java.base/java.util.ArrayList.get(ArrayList.java:428) ~[?:?]
        at EssentialsX.jar//com.earth2me.essentials.AlternativeCommandsHandler.getAlternative(AlternativeCommandsHandler.java:133) ~[?:?]
        at EssentialsX.jar//com.earth2me.essentials.EssentialsPlayerListener$CommandSendFilter.isEssentialsCommand(EssentialsPlayerListener.java:1279) ~[?:?]
        at EssentialsX.jar//com.earth2me.essentials.EssentialsPlayerListener$CommandSendFilter.lambda$apply$0(EssentialsPlayerListener.java:1254) ~[?:?]
        at EssentialsX.jar//net.ess3.provider.providers.PaperCommandSendListenerProvider.lambda$onAsyncCommandSend$0(PaperCommandSendListenerProvider.java:28) ~[?:?]
        at java.base/java.util.Collection.removeIf(Collection.java:581) ~[?:?]
        at EssentialsX.jar//net.ess3.provider.providers.PaperCommandSendListenerProvider.onAsyncCommandSend(PaperCommandSendListenerProvider.java:28) ~[?:?]
        at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:71) ~[paper-api-26.2.build.112-stable.jar:?]
        at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-26.2.jar:26.2-112-c9e894d]
        at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:131) ~[paper-26.2.jar:26.2-112-c9e894d]
        at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:627) ~[paper-api-26.2.build.112-stable.jar:?]
        at org.bukkit.event.Event.callEvent(Event.java:46) ~[paper-api-26.2.build.112-stable.jar:?]
        at net.minecraft.commands.Commands.sendAsync(Commands.java:517) ~[paper-26.2.jar:26.2-112-c9e894d]
        at net.minecraft.commands.Commands.lambda$sendCommands$0(Commands.java:491) ~[paper-26.2.jar:26.2-112-c9e894d]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) ~[?:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) ~[?:?]
        at java.base/java.lang.Thread.run(Thread.java:1474) ~[?:?]

Potential cause: getAlternative guards for an empty list on entry, then prunes dead WeakReferences inside the loop, then reads element 0 without re-checking:

if (commands == null || commands.isEmpty()) return null;   // passes: entries exist
if (commands.size() == 1) return commands.get(0).get();
for (Iterator<WeakReference<Command>> it = commands.iterator(); it.hasNext();) {
    Command cmd = it.next().get();
    if (cmd == null) { it.remove(); continue; }             // may empty the list
    if (cmd.getName().equalsIgnoreCase(label)) return cmd;
}
return commands.get(0).get();                               // line 133 — throws when emptied

If every entry in a bucket is a collected reference, the loop empties the list and get(0) throws. The WeakReferences came in with #5539.

Alternatively, this could also be a concurrency issue. According to the stack trace, commands is a regular ArrayList that gets read and modified non-atomically in an async event.

Impact: caught per-event, so the join completes. What's lost is Essentials filtering its own aliases out of the command list sent to the client, i.e. tab completion for that player.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug: unconfirmedPotential bugs that need replicating to verify.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions