Description
This error happened on my PaperMC server, but I believe it to be a brigadier issue.
Step 1: Make a new com.mojang.brigadier.suggestion.SuggestionsBuilder
Step 2: builder.suggest specific mix of integers and strings
Step 3: builder.buildFuture().join;
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.base/java.util.TimSort.mergeLo(TimSort.java:781) ~[?:?]
at java.base/java.util.TimSort.mergeAt(TimSort.java:518) ~[?:?]
at java.base/java.util.TimSort.mergeCollapse(TimSort.java:448) ~[?:?]
at java.base/java.util.TimSort.sort(TimSort.java:245) ~[?:?]
at java.base/java.util.Arrays.sort(Arrays.java:1308) ~[?:?]
at java.base/java.util.ArrayList.sort(ArrayList.java:1804) ~[?:?]
at com.mojang.brigadier.suggestion.Suggestions.create(Suggestions.java:99) ~[brigadier-1.3.10.jar:?]
at com.mojang.brigadier.suggestion.SuggestionsBuilder.build(SuggestionsBuilder.java:51) ~[brigadier-1.3.10.jar:?]
at com.mojang.brigadier.suggestion.SuggestionsBuilder.buildFuture(SuggestionsBuilder.java:55) ~[brigadier-1.3.10.jar:?]
at net.minecraft.server.network.ServerGamePacketListenerImpl.handleCustomCommandSuggestions0(ServerGamePacketListenerImpl.java:844) ~[paper-1.21.1.jar:1.21.1-DEV-755a775]
at net.minecraft.server.network.ServerGamePacketListenerImpl.lambda$handleCustomCommandSuggestions$1(ServerGamePacketListenerImpl.java:811) ~[paper-1.21.1.jar:1.21.1-DEV-755a775]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[?:?]
The issue occurs in Suggestions.create()
final List<Suggestion> sorted = new ArrayList<>(texts);
sorted.sort((a, b) -> a.compareToIgnoreCase(b));
when sorted is a mix of IntegerSuggestion and Suggestion:
In suggestion:
public int compareToIgnoreCase(final Suggestion b) {
return text.compareToIgnoreCase(b.text);
}
is used to compare whereas in integer suggestion:
@Override
public int compareTo(final Suggestion o) {
if (o instanceof IntegerSuggestion) {
return Integer.compare(value, ((IntegerSuggestion) o).value);
}
return super.compareTo(o);
}
@Override
public int compareToIgnoreCase(final Suggestion b) {
return compareTo(b);
}
is used to compare them.
The exception is due to transitivity violations in the comparator logic:
Comparator Contract Violations:
Transitivity: If a < b and b < c, then a < c must hold.
In the classes:
IntegerSuggestion objects are compared based on value.
Suggestion objects are compared based on text.
Mixing comparisons between these two types can lead to scenarios where transitivity is violated.
Example Scenario:
Let s1 be a Suggestion with text = "apple".
Let i1 be an IntegerSuggestion with value = 2 (text = "2").
Let i2 be an IntegerSuggestion with value = 3 (text = "3").
Comparisons:
i1.compareToIgnoreCase(i2) compares based on value: 2 < 3 → returns -1 (i1 < i2).
i2.compareToIgnoreCase(s1) compares based on text: "3".compareToIgnoreCase("apple") → positive value (i2 > s1).
Transitivity Violation: From i1 < i2 and i2 > s1, transitivity implies i1 < s1. However, i1.compareToIgnoreCase(s1) compares based on text: "2".compareToIgnoreCase("apple") → positive value (i1 > s1), which contradicts the transitivity requirement.
Here is java code that replicates the issue every time
public class Main2 {
public static void main(String[] args) {
String[] suggestions = {
"124", "328 art", "64", "354", "98", "35", "101", "2", "110", "11", "74", "105", "150", "192", "30", "301",
"103", "102", "40", "70", "24", "115", "122", "112", "15", "222", "99", "80", "326 <farm>", "20", "111", "205",
"8", "71", "329 mine house", "6", "107", "51", "403 end tp", "104", "52", "235", "236", "203", "94",
"327", "25", "4", "95", "304", "323", "33", "88", "93", "36", "45", "26", "82", "204", "53", "333 x stash",
"32", "69", "234", "60", "335 treehouse", "47", "72", "210", "41", "325", "66", "5", "322", "332", "300",
"206", "191", "75", "202", "302", "14", "882 weird lang", "324", "27", "19", "90", "3", "339 check!!!",
"151", "169", "119", "190", "120", "201", "7", "13", "230", "81", "83", "239 mf", "23", "21", "320", "16",
"97", "50", "65", "231", "114", "22", "303", "18", "5", "12", "91", "17", "121", "9", "125", "123", "73", "44124"
};
SuggestionsBuilder builder0 = new SuggestionsBuilder("/", "/".length());
for (String element : suggestions) {
try {
int intSuggestion = Integer.parseInt(element);
builder0.suggest(intSuggestion);
} catch (NumberFormatException ignored) {
builder0.suggest(element);
}
}
// Build the suggestions, error happens here
try {
com.mojang.brigadier.suggestion.Suggestions futureSuggestions = builder0.buildFuture().join();
} catch (IllegalArgumentException illegalArgumentException) {
System.out.println("tim sort exception?");
illegalArgumentException.printStackTrace();
}
}
}