-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Don't delegate foreign private names to noSuchMethod
#49687
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
Comments
This would be really nice to land. Can we run a test to double check impact on flutter and google tests? @srawlins - any concerns re mocking? |
No concerns from me! |
@vsmenon and I spoke about this offline. It's complicated, but here's a summary:
|
How about not delegating any private names to I mean, why is this even legal to write in Dart today: class A {
void _foo() {}
void bar() {
_foo();
}
}
void main() {
dynamic x = A();
x._foo(); // why not make it illegal to call private stuff on dynamic variables everywhere?
} What if assigning to a dynamic variable always (even in the same file) prevented you from accessing the private interface? |
@nielsenko - yeah, I suspect that'd be a much bigger breaking change to land. Perhaps worth filing as a separate suggestion here though: |
In a future release of Dart, if a class containing private members is *implemented* (rather than *extendeed*), any attempt to invoke one of the private members will result in a runtime error (see dart-lang/sdk#49687). This is necessary in order to soundly support field promotion (dart-lang/language#2020). The runtime error will most likely be a new exception type, not `NoSuchMethodError`. So, to avoid breaking the `SharedPreferencesStorePlatform.instance` setter, we need to generalize it to consider any exception thrown by `_verifyProvidesDefaultImplementations` as an indication that the `SharedPreferencesStorePlatform` has been illegally implemented. In the non-error case, `_verifyProvidesDefaultImplementations` does nothing, so this generalization should be safe.
Thanks to a lot of help from @a-siva, @iinozemtsev, @godofredoc, and @athomas, this change has now been tested throughly, both in Google's internal codebase and in the open source tests for Flutter and Dart. The only other breakage, other than those I've mentioned previously, was a breakage to flutter plugins. I've prepared a fix for that (flutter/plugins#6318), but @stuartmorgan is going to work on a better fix, with an ETA of Monday. All the other fixes are either landed or making good progress. So I think we are good to move forward with this change. |
lgtm! |
@grouma could you give this breaking change request a look? |
Considering,
LGTM. |
If a concrete class implements an interface containing a name that's private to a different library, any attempt to invoke that name will result in an exception getting thrown. Previously, such attempts would result in the call being diverted to noSuchMethod. This change closes a loophole in Dart's privacy system, and paves the way for a future implementation of promotion for private final fields (see dart-lang/language#2020). Bug: #49687 Change-Id: Ie55805e0fc77dc39713761a80a42c28bd0504722 Tested: language tests Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255640 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Lasse Nielsen <[email protected]>
Addressed in 5490675. |
Change
If a concrete class implements an interface containing a name that's private to a different library, any attempt to invoke that name will result in an exception getting thrown.
Previously, such attempts would result in the call being diverted to
noSuchMethod
.Rationale
1. Closes a loophole in Dart's privacy system
Without this change,
noSuchMethod
could be used to provide an alternative behavior for a private name belonging to a different library. In effect, this is a loophole in the privacy system, because it's natural for the author of a library to assume that any time they invoke a private name, they are invoking their own code. For example:Without the proposed change, running this code prints the surprising message
sneaky override of _foo() invoked
, because whenuseA
attempts to call_foo
, the call gets dispatched toE.noSuchMethod
. With the change, running this code will cause an exception to be thrown.2. Makes field promotion possible
This change will allow us to implement type promotion of fields in a way that is sound, without forcing the compiler to analyze the user's whole program. For example, once field promotion is implemented, the following will be possible:
Without the change, the above code would be unsound, because it would be possible for code in another library to override
_i
usingnoSuchMethod
, changing its behavior to something that returnsnull
sometimes and non-null
other times.See dart-lang/language#2020 for more details.
Impact
This change breaks an uncommon mocking pattern, where a library contains a class with a private member, and that private member is invoked from static code, or code in some other class. For example:
This code works today because even though mockito code generation cannot genrate a stub for the
_init
method and the_inUse=
setter (because they are private to another library), it's still able to generate an implementation ofnoSuchMethod
that intercepts the calls to them. The mock speconMissingStub: OnMissingStub.returnDefault
ensures that these intercepted calls will returnNull
, so the test passes.After the change, a test like this one will fail because the attempts to call
_init
and_inUse=
will result in an exception being thrown.Based on studying Google's internal codebase, I believe that this pattern arises quite rarely.
Mitigation
For the rare users with mocks affected by this change, there are several options:
@visibleForTesting
to discourage clients from using it. In other words, the example from the "Impact" section could be rewritten like this:@visibleForTesting
to discourage clients from accessing it. In other words, the example from the "Impact" section could be rewritten like this:The text was updated successfully, but these errors were encountered: