Thanks to visit codestin.com
Credit goes to github.com

Skip to content

KeySet.hashCode depends on ordering of keys provided to constructor #38919

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

Closed
shyndman opened this issue Aug 20, 2019 · 19 comments
Closed

KeySet.hashCode depends on ordering of keys provided to constructor #38919

shyndman opened this issue Aug 20, 2019 · 19 comments
Labels
a: desktop Running on desktop framework flutter/packages/flutter repository. See also f: labels.

Comments

@shyndman
Copy link
Contributor

Simple example:

final a = LogicalKeySet(LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.arrowRight);
final b = LogicalKeySet(LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.shiftLeft);
print(a == b); // true 
print(a.hashCode == b.hashCode); // false 

It appears that the Set literal's iteration order is influenced by insertion order. See use of hashList in KeySet.hashCode.

cc @gspencergoog

@shyndman
Copy link
Contributor Author

shyndman commented Aug 20, 2019

Oh, slightly more info as to why this matters.

The ShortcutManager performs a hash lookup of the currently pressed keys to determine whether there's a matching intent. With the possibility of a hash mismatch, the intent may not found.

@gspencergoog gspencergoog added a: desktop Running on desktop framework flutter/packages/flutter repository. See also f: labels. labels Aug 21, 2019
@gspencergoog
Copy link
Contributor

Oh, my. OK, we'll have to do something to fix that (probably sort them when inserting, or just use a different data structure).

@jonahwilliams
Copy link
Member

By default the Set and Map types or via literals in dart preserve insertion order. To get an unordered set you'd have to explicitly use HashSet or HashMap

@gspencergoog
Copy link
Contributor

Well, but would iteration order matter in a hash set? Or, even better, does hashCode on a HashSet provide a hash of the contents?

@jonahwilliams
Copy link
Member

Iteration order is the preserved insertion order for LinkedHashSet and LinkedHashMap (the defaults). Map/Sets in general are mutable and don't have a meaningful hashCode.

@shyndman
Copy link
Contributor Author

shyndman commented Aug 21, 2019

One thought is to use a TreeSet sorted on keyId. That would make iteration order consistent regardless of insertion order, and therefore a consistent value produced by hashList

Edit: The class is called SplayTreeSet in dart:collection

@jonahwilliams
Copy link
Member

Do the key sets always contain 4 keys?

@shyndman
Copy link
Contributor Author

No, at most 4 in the default constructor, but there's a fromSet constructor for arbitrary length.

@gspencergoog
Copy link
Contributor

gspencergoog commented Aug 21, 2019

I have a PR that fixes this. HashSet is sufficient, since the iteration order seems to depend on the hash value of the elements.

@gspencergoog
Copy link
Contributor

From the docs for HashSet:

"The iteration order of the set is not specified and depends on the hashcodes of the provided elements. However, the order is stable: multiple iterations over the same set produce the same order, as long as the set is not modified."

This is perfect for this use case.

@shyndman
Copy link
Contributor Author

What would happen if two hash codes collide? It may depend on the implementation of the map. I'm going to throw together a dartpad and see if I can break it.

@jonahwilliams
Copy link
Member

FWIW SplayTreeSet is as slow as molasses

@shyndman
Copy link
Contributor Author

shyndman commented Aug 21, 2019

OK, it's a contrived example, but demonstrative. Using a HashSet, I forced a hash collision by having two elements return the same hashCode, but register as unique in their operator ==. In this case, iteration order reflects insertion order.

It's difficult to say how this might happen in a real-world scenario, but it may very well be possible. Whether or not there's a collision of this kind is presumably based on the number of buckets the HashSet uses.

@shyndman
Copy link
Contributor Author

FWIW SplayTreeSet is as slow as molasses

Just a suggestion. Might be useful just to get things into sorted order, and generating the hashCode up front. You can ditch it after that, since the set is immutable and the only check you need afterwards is set equality.

@shyndman
Copy link
Contributor Author

Scratch that. After trying pretty hard to get a collision in a real-world situation, I couldn't do it. It's probably not a big deal.

@gspencergoog
Copy link
Contributor

Yeah, that definitely won't happen with LogicalKeyboardKeys. They use their unique keyId for both the operator== and the hashCode.

And if something does return false from operator= and produce the same hashCode, it is violating the contract for the hashCode property, which states "Hash codes must be the same for objects that are equal to each other according to operator ==. "

@shyndman
Copy link
Contributor Author

shyndman commented Aug 23, 2019

If you read that carefully, you'll see it doesn't really say that.

If they are equal, they must return the same hashCode, but it doesn't say anything about the behavior when they're unequal. That's why hash sets and maps rely on both hashCode and equality to determine membership.

Thanks for the PR though. Appreciate it.

@gspencergoog
Copy link
Contributor

My apologies, you're correct.

In this case, LogicalKeyboardKey is always going to return a different hash code if the two keys have different keyIds, so it isn't a concern.

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: desktop Running on desktop framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

No branches or pull requests

3 participants