-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Replace NavigatorObserver._navigator with a static expando. #109238
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
The dart language team will soon be changing the behavior of the language so that an invocation of a private class members won't get dispatched to a `noSuchMethod` method in another library; instead an exception will be thrown. This is a necessary prerequisite to allowing promotion of private fields (dart-lang/language#2020). It also closes an important privacy loophole in the language, by ensuring that the user can see all the possible behaviors of a private member invocation without having to look outside of the library. This has an important consequence for tests using mockito, in the scenario where a private class member is referenced from outside of its containing class, and the containing class instance is replaced with a mock, but the reference isn't replaced with a mock. Previously, in this scenario, the invocation would be dispatched to Mock.noSuchMethod invocation; with this change it will throw an exception. This situation arises with NavigatorObserver._navigator, which is referenced from the NavigatorState class; if a test mocks NavigatorObserver but not NavigatorState, it will likely fail. It could be argued that such tests should be rewritten to use a less brittle mocking strategy, but there are enough examples of this pattern in Google's internal code base that it seems likely that it exists in code belonging to other customers too. To avoid breaking these customers, we replace NavigatorObserver._navigator with a static expando. Since references to static class members aren't affected by mocking, this will ensure that the upcoming dart language change won't trigger any new exceptions in code following this testing pattern. There is no behavioral change for production code.
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
goderbauer
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.
Could you add a test for this that makes sure a class implementing NavigatorObserver and noSuchMethod can be used as a Navigator.observer without problems? That way, if somebody tries to refactor this back to what it was we will have a failing test to warn them once field promotion lands.
| NavigatorState? get navigator => _navigator; | ||
| NavigatorState? _navigator; | ||
| NavigatorState? get navigator => _navigators[this]; | ||
| static final _navigators = Expando<NavigatorState>(); |
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.
Could you leave a comment on this Expando property describing why we are doing this. I am sure the next guy looking at this will be somewhat confused as to why we do this 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.
Done.
| NavigatorState? get navigator => _navigator; | ||
| NavigatorState? _navigator; | ||
| NavigatorState? get navigator => _navigators[this]; | ||
| static final _navigators = Expando<NavigatorState>(); |
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 - as the analyzer check will hopefully remind you - this field needs to be typed)
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.
Ok, done. I puzzled for a while over what the test should do, and what expectations it should check. I finally settled on replicating the actions in one of the other simple navigator tests, and testing that the sequence of invocations passed to noSuchMethod is as expected. Let me know if this seems reasonable. |
goderbauer
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.
LGTM once the checks are happy.
This change replaces
NavigatorObserver._navigator(an instance field that points from aNavigatorObserverto theNavigatorStateit's observing) with an expando that maps fromNavigatorObservertoNavigatorState. There is no change to the public API, and no change to the behavior of users' production code.The change ensures that if a customer's test replaces an instance of
NavigatorObserverwith a mock, but doesn't replaceNavigatorStatewith a mock, the code inNavigatorStatewon't try to access the private_navigatorfield inNavigatorObserver. This is important because an upcoming change to the dart language is going to cause such accesses to throw exceptions (see #109237 and dart-lang/language#2020 for details).Fixes #109237.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.