Skip to content

Commit e70bdb8

Browse files
authored
Fix IndexOutOfBoundsException in AlternativeCommandsHandler.getAlternative (#6609)
<!-- EssentialsX bug fix submission guide ==================================== NOTE: Failure to fill out this template properly may result in your PR being delayed or ignored without warning. NOTE: Don't type between any arrows in the template, as this text will be hidden. This includes this header block and any other explanation text blocks. Want to discuss your PR before submitting it? Join the EssentialsX Development server: https://discord.gg/CUN7qVb EssentialsX is GPL ------------------ By contributing to EssentialsX, you agree to license your code under the GNU General Public License version 3, which can be found at the link below: https://github.com/EssentialsX/Essentials/blob/2.x/LICENSE Instructions ------------ If you are submitting a bug fix, please follow the following steps: 1. Fill out the template in full. This includes providing screenshots and a link to the original bug report. If there isn't an existing bug report, we recommend opening a new detailed bug report BEFORE opening your PR to fix it, else your PR may be delayed or rejected without warning. You can open a new bug report by following this link: https://github.com/EssentialsX/Essentials/issues/new/choose 2. When linking logs or config files, do not attach them to the post! Copy and paste any logs into https://gist.github.com/, then paste a link to them in the relevant parts of the template. Do not use Hastebin or Pastebin, as this can cause issues with future reviews. DO NOT drag logs directly into this text box, as we cannot read these! 3. If you are fixing a performance issue, please include a link to a Timings and/or profiler report, both before and after your PR. 4. If you are fixing a visual bug, such as in commands, please include screenshots so that we can more easily review the proposed fix. (You can drag screenshots into the bottom of the editor.) --> ### Information <!-- Replace #nnnn with the number of the original issue. If this PR fixes multiple issues, you should repeat the phrase "fixes #nnnn" for each issue. --> This PR fixes #6608. ### Details **Proposed fix:** <!-- Type a description of your proposed fix below this line. --> `getAlternative` prunes collected `WeakReference`s through its own iterator, which can remove every entry in a bucket. The following `commands.get(0)` then throws `IndexOutOfBoundsException`. This stores the first live alias during the iteration and returns that instead, or `null` when nothing live remains, which is what the caller already tests for (`getAlternative(label) == null` in `EssentialsPlayerListener.CommandSendFilter`). Confirmed with an instrumented build that logged the list sizes around the prune loop: ``` [6608] label=w thread=Paper Async Command Builder Thread Pool - 0 entry=2 removedByUs=2 after=0 expected=0 ``` Both references in the `w` bucket had been collected and the loop removed both. Note: this does not address the possible concurrent modification also raised in #6608 (`altCommands` is a plain `HashMap` of `ArrayList`, modified from the async command-send path). That needs a separate synchronisation decision. **Environments tested:** <!-- Type the OS you have used below. --> OS: Debian 12 <!-- Type the JDK version (from java -version) you have used below. --> Java version: 25.0.4 2026-07-21 LTS <!-- Put an "x" inside the boxes for the server software you have tested this bug fix on. If this feature does not apply to a server, strike through the server software using ~~strikethrough~~. If you have tested on other environments, add a new line with relevant details. --> - [x] Most recent Paper version (26.2, git-Paper-112) - [ ] CraftBukkit/Spigot/Paper 1.12.2 - [ ] CraftBukkit 1.8.8 **Demonstration:** <!-- Below this block, include screenshots/log snippets from before and after as necessary. If you have created or used a test case plugin, please link to a download of the plugin, source code and exact version used where possible. --> Before, on every boot with a player join (5 consecutive boots, EssentialsX 2.22.0 and 2.22.1-dev+17-382de4f, same server and plugin set): ``` [ERROR]: Could not pass event AsyncPlayerSendCommandsEvent to Essentials java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0 at java.util.ArrayList.get(ArrayList.java:428) at com.earth2me.essentials.AlternativeCommandsHandler.getAlternative(AlternativeCommandsHandler.java:133) at com.earth2me.essentials.EssentialsPlayerListener$CommandSendFilter.isEssentialsCommand(EssentialsPlayerListener.java:1279) at net.ess3.provider.providers.PaperCommandSendListenerProvider.onAsyncCommandSend(PaperCommandSendListenerProvider.java:28) ... ``` Cause isolated with a temporary instrumented build logging list sizes around the prune loop: ``` [6608] label=w thread=Paper Async Command Builder Thread Pool - 0 entry=2 removedByUs=2 after=0 expected=0 ``` Both `WeakReference` in the `w` bucket had been collected and the loop removed both. After, with this fix (2.22.1-dev+18), same server, player join: ``` IndexOutOfBoundsException: 0 AlternativeCommandsHandler references: 0 Could not pass event AsyncPlayerSendCommandsEvent: 0 ```
1 parent 2dbd63a commit e70bdb8

1 file changed

Lines changed: 8 additions & 2 deletions

File tree

‎Essentials/src/main/java/com/earth2me/essentials/AlternativeCommandsHandler.java‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public Command getAlternative(final String label) {
116116
}
117117

118118
// return the first command that is not an alias
119+
Command firstAlias = null;
120+
119121
final Iterator<WeakReference<Command>> iterator = commands.iterator();
120122
while (iterator.hasNext()) {
121123
final Command cmd = iterator.next().get();
@@ -127,10 +129,14 @@ public Command getAlternative(final String label) {
127129
if (cmd.getName().equalsIgnoreCase(label)) {
128130
return cmd;
129131
}
132+
133+
if (firstAlias == null) {
134+
firstAlias = cmd;
135+
}
130136
}
131137

132-
// return the first alias
133-
return commands.get(0).get();
138+
// return the first alias, or null if every reference in this bucket has been collected
139+
return firstAlias;
134140
}
135141

136142
public void executed(final String label, final Command pc) {

0 commit comments

Comments
 (0)