Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit feed66b

Browse files
[plugin_platform_interface] Switch PlatformInterface._instanceToken to an expando. (#6411)
This change replaces `PlatformInterface._instanceToken` (an instance field that points from a `PlatformInterface` to its corresponding token) with an expando that maps from `PlatformInterface` to `Object`. There is no change to the public API, and no change to the behavior of users' production code. This change ensures that if a customer tries to implement `PlatformInterface` using `implements` rather than `extends`, the code in `PlatformInterface._verify` won't try to access the private `_instanceToken` field in `PlatformInterface`. This is important because an upcoming change to the dart language is going to cause such accesses to throw exceptions rather than deferring to `noSuchMethod` (see dart-lang/language#2020 for details). Fixes flutter/flutter#109339.
1 parent dc3bfc6 commit feed66b

File tree

4 files changed

+34
-6
lines changed

4 files changed

+34
-6
lines changed

packages/plugin_platform_interface/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
## NEXT
1+
## 2.1.3
22

33
* Minor fixes for new analysis options.
44
* Adds additional tests for `PlatformInterface` and `MockPlatformInterfaceMixin`.
5+
* Modifies `PlatformInterface` to use an expando for detecting if a customer
6+
tries to implement PlatformInterface using `implements` rather than `extends`.
7+
This ensures that `verify` will continue to work as advertized after
8+
https://github.com/dart-lang/language/issues/2020 is implemented.
59

610
## 2.1.2
711

packages/plugin_platform_interface/lib/plugin_platform_interface.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,20 @@ abstract class PlatformInterface {
4444
/// derived classes.
4545
///
4646
/// @param token The same, non-`const` `Object` that will be passed to `verify`.
47-
PlatformInterface({required Object token}) : _instanceToken = token;
47+
PlatformInterface({required Object token}) {
48+
_instanceTokens[this] = token;
49+
}
4850

49-
final Object? _instanceToken;
51+
/// Expando mapping instances of PlatformInterface to their associated tokens.
52+
/// The reason this is not simply a private field of type `Object?` is because
53+
/// as of the implementation of field promotion in Dart
54+
/// (https://github.com/dart-lang/language/issues/2020), it is a runtime error
55+
/// to invoke a private member that is mocked in another library. The expando
56+
/// approach prevents [_verify] from triggering this runtime exception when
57+
/// encountering an implementation that uses `implements` rather than
58+
/// `extends`. This in turn allows [_verify] to throw an [AssertionError] (as
59+
/// documented).
60+
static final Expando<Object> _instanceTokens = Expando<Object>();
5061

5162
/// Ensures that the platform instance was constructed with a non-`const` token
5263
/// that matches the provided token and throws [AssertionError] if not.
@@ -89,10 +100,10 @@ abstract class PlatformInterface {
89100
return;
90101
}
91102
if (preventConstObject &&
92-
identical(instance._instanceToken, const Object())) {
103+
identical(_instanceTokens[instance], const Object())) {
93104
throw AssertionError('`const Object()` cannot be used as the token.');
94105
}
95-
if (!identical(token, instance._instanceToken)) {
106+
if (!identical(token, _instanceTokens[instance])) {
96107
throw AssertionError(
97108
'Platform interfaces must not be implemented with `implements`');
98109
}

packages/plugin_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+
1515
# be done when absolutely necessary and after the ecosystem has already migrated to 2.X.Y version
1616
# that is forward compatible with 3.0.0 (ideally the ecosystem have migrated to depend on:
1717
# `plugin_platform_interface: >=2.X.Y <4.0.0`).
18-
version: 2.1.2
18+
version: 2.1.3
1919

2020
environment:
2121
sdk: ">=2.12.0 <3.0.0"

packages/plugin_platform_interface/test/plugin_platform_interface_test.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class SamplePluginPlatform extends PlatformInterface {
2121
class ImplementsSamplePluginPlatform extends Mock
2222
implements SamplePluginPlatform {}
2323

24+
class ImplementsSamplePluginPlatformUsingNoSuchMethod
25+
implements SamplePluginPlatform {
26+
@override
27+
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
28+
}
29+
2430
class ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin extends Mock
2531
with MockPlatformInterfaceMixin
2632
implements SamplePluginPlatform {}
@@ -98,6 +104,13 @@ void main() {
98104
}, throwsA(isA<AssertionError>()));
99105
});
100106

107+
test('prevents implmentation with `implements` and `noSuchMethod`', () {
108+
expect(() {
109+
SamplePluginPlatform.instance =
110+
ImplementsSamplePluginPlatformUsingNoSuchMethod();
111+
}, throwsA(isA<AssertionError>()));
112+
});
113+
101114
test('allows mocking with `implements`', () {
102115
final SamplePluginPlatform mock =
103116
ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin();

0 commit comments

Comments
 (0)