-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix assertion failure when MethodCall args are Iterable #12277
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
Conversation
760fb1f to
74ef65d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes hashCode inconsistent with equals. For lists and map arguments, you'd basically only have consistency with const expressions. As an example,
final c1 = new MethodCall('foo', {'bar':'baz'});
final c2 = new MethodCall('foo', {'bar':'baz'});
assert(c1 == c2);
assert(c1.hashCode() != c2.hashCode());I think we should change hashValues to work with any type instead. Mostly because I'm sure I've made the same mistake in a number of other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was already broken before, since hashValues intentionally doesn't do anything with lists and doesn't do anything deep with maps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hashValues intentionally asserts with Iterables because you almost never want to compare the actual object of a list. It definitely shouldn't automatically be deep because that's very expensive and expensive things should look expensive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The real solution here is to not use "dynamic". Every time we use "dynamic" we end up with bugs because it basically turns off the analyzer. Do we have to make the arguments dynamic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also do we need MethodCall to be comparable in this deep way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your time, Ian.
This was already broken before [...]
Yes, you're right of course. My original code expected hashValues to go deep.
hashValues intentionally asserts with Iterables because you almost never want to
compare the actual object of a list. It definitely shouldn't automatically be deep
because that's very expensive and expensive things should look expensive.
Expecting equality and hash code to be deep is a remnant from my Java days where the Collections framework does it that way. I repeatedly find myself missing that behavior of Dart lists and maps.
The real solution here is to not use "dynamic". Every time we use "dynamic" we
end up with bugs because it basically turns off the analyzer. Do we have to make
the arguments dynamic?
The argument of a MethodCall could have been made List<dynamic> or Map<String, dynamic>, bringing overhead in code and at runtime to situations where we just want to pass a bool/int/String value. And I guess it would only move the problem with dynamic to the list entries or map values? In the end, the argument is dynamic for the same reason hashValues is parameterized with Object: We don't have a type that captures precisely the valid method call arguments (trees of nested lists and maps with bool/int/String leaves), nor a type that captures the non-Iterables accepted by hashValues.
Also do we need MethodCall to be comparable in this deep way?
I originally gave MethodCall a custom equals (and therefore hashCode) to facilitate writing unit tests. I don't have a production code use case, but one could conceivably have a Map<MethodCall, dynamic> cache in a situation where calculating the result is expensive. Whether we want to support that use case is an open question. If not, we could (remove the custom equality and hash and fix up the unit tests, or) accept a hash code with more collisions:
int get hashCode => method.hashCode;If we do want to support MethodCalls as map keys, we could instead do
int get hashCode => hashValues(method, _deepHash(argument));I'd vote for adding deep equality and hash code functions to dart:ui, possibly naming them treeEquals and treeHashCode or similar to reflect that they do recursive descent through lists and maps, and that they would fail on cyclic values. Then they look expensive, and we can use them for things like MethodCall arguments where the tree can reasonably be expected to be small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd rather just remove these ==/hashCode methods and change the unit tests to manually verify the arguments are what they expect. It should have pretty minimal impact on the tests (and if it's a lot we can always write a custom matcher), and would avoid anyone relying on comparing method calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your call :-) @cbracken Do you want to change this PR to do as Ian suggests? Otherwise, I'll be happy to do it in another PR and then I think we should close this one.
|
@cbracken are you still working on this? |
|
@Hixie thanks for the ping. Fired this PR off in a few mins before running out the door one night so I wouldn't forget about it then promptly did just that. I'll blow away the equals/hashCodes and patch up the tests. |
98a4c72 to
1f37936
Compare
|
Still WIP, but quick reworked the existing code into a matcher that effectively keeps the same deep matching behaviour for convenience when testing but kills off |
1f37936 to
99d40c8
Compare
|
PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constructor first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mravn-google
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change and should be announced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constructor before fields (I guess?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prettier indentation with trailing comma here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intentional to make the parens symmetric with the opening line.
Since MethodCall equality checks are limited to test scenarios, this patch replaces them with an equivalent test matcher. At present MethodCalls are always used in scenarios where indentity-based equality/hashing is appropriate. This change avoids an assertion failure when MethodCall args are Iterable (possible since args are of type dyanmic), and hashValue() from dart:ui asserts that its input is not an Iterable. The alternative of implementing support for deep equality in dart:ui was rejected on the basis that if we're to encourage performant code, expensive checks should be obviously-expensive to the author.
99d40c8 to
44f1344
Compare
|
Breaking change announcement sent. |
Since MethodCall equality checks are limited to test scenarios, this patch replaces them with an equivalent test matcher. At present MethodCalls are always used in scenarios where indentity-based equality/hashing is appropriate. This change avoids an assertion failure when MethodCall args are Iterable (possible since args are of type dyanmic), and hashValue() from dart:ui asserts that its input is not an Iterable. The alternative of implementing support for deep equality in dart:ui was rejected on the basis that if we're to encourage performant code, expensive checks should be obviously-expensive to the author.
…IOSGLContext objects (flutter#12277)" (flutter/engine#12773)
[email protected]:flutter/engine.git/compare/7d67e275ff82...8aa4732 git log 7d67e27..8aa4732 --no-merges --oneline 2019-10-03 [email protected] Fix Metal builds. (flutter/engine#12777) 2019-10-03 [email protected] Revert "Manage resource and onscreen contexts using separate IOSGLContext objects (#12277)" (flutter/engine#12773) 2019-10-03 [email protected] Roll src/third_party/dart afac6a3714..07a63a17a4 (6 commits) 2019-10-03 [email protected] roll buildroot to 01e9235 (flutter/engine#12771) 2019-10-03 [email protected] Create a package-able incremental compiler (flutter/engine#12681) 2019-10-03 [email protected] add windows embedding test (flutter/engine#12423) 2019-10-03 [email protected] Roll fuchsia/sdk/core/mac-amd64 from g-PD1... to wYLiQ... (flutter/engine#12770) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
[email protected]:flutter/engine.git/compare/7d67e275ff82...8aa4732 git log 7d67e27..8aa4732 --no-merges --oneline 2019-10-03 [email protected] Fix Metal builds. (flutter/engine#12777) 2019-10-03 [email protected] Revert "Manage resource and onscreen contexts using separate IOSGLContext objects (flutter#12277)" (flutter/engine#12773) 2019-10-03 [email protected] Roll src/third_party/dart afac6a3714..07a63a17a4 (6 commits) 2019-10-03 [email protected] roll buildroot to 01e9235 (flutter/engine#12771) 2019-10-03 [email protected] Create a package-able incremental compiler (flutter/engine#12681) 2019-10-03 [email protected] add windows embedding test (flutter/engine#12423) 2019-10-03 [email protected] Roll fuchsia/sdk/core/mac-amd64 from g-PD1... to wYLiQ... (flutter/engine#12770) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
MethodCall.arguments is of type dynamic, which allows for Iterables. Its
hashCode getter was implemented with hashValue() which asserts that its
input is not an Iterable. Instead XOR the method name hashCode with the
identityHashCode of the arguments.