Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comparing string and integer argument types can result in Comparison method violations #154

Open
NewwindServer opened this issue Oct 28, 2024 · 0 comments

Comments

@NewwindServer
Copy link

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();
        }
    }
}
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

No branches or pull requests

1 participant