Skip to content

Commit 2dff621

Browse files
[pigeon] Prevents premature finalization from unused Dart instance for ProxyApis (#9231)
1. The Dart `InstanceManager.addHostCreatedInstance` is only ever used to respond to native calls that create a new instance right before the instance is returned in a host method call or passed in a flutter method call. So, `addHostCreatedInstance` now only adds a strong referenced instance and not the weak referenced instance. This will ensure the weak referenced instance is not created until it is being passed to a user immediately. 2. The Dart `InstanceManager.remove` now asserts that the strong referenced instance isn't removed before the weak referenced instance. This should prevent future scenarios where the user is holding a weak referenced instance while the native side no longer has any reference. Fixes flutter/flutter#168531 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 63843bb commit 2dff621

File tree

12 files changed

+169
-49
lines changed

12 files changed

+169
-49
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 25.3.2
2+
3+
* [dart] Fixes null pointer crashes/exceptions caused by premature finalization of Dart instances
4+
for ProxyApis.
5+
16
## 25.3.1
27

38
* [kotlin] Fixes Kotlin InstanceManager not properly removing callbacks from handler.

packages/pigeon/lib/src/dart/templates.dart

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,17 @@ class $dartInstanceManagerClassName {
110110
///
111111
/// Returns the randomly generated id of the [instance] added.
112112
int addDartCreatedInstance($proxyApiBaseClassName instance) {
113+
assert(getIdentifier(instance) == null);
114+
113115
final int identifier = _nextUniqueIdentifier();
114-
_addInstanceWithIdentifier(instance, identifier);
116+
_identifiers[instance] = identifier;
117+
_weakInstances[identifier] =
118+
WeakReference<$proxyApiBaseClassName>(instance);
119+
_finalizer.attach(instance, identifier, detach: instance);
120+
121+
final $proxyApiBaseClassName copy = instance.pigeon_copy();
122+
_identifiers[copy] = identifier;
123+
_strongInstances[identifier] = copy;
115124
return identifier;
116125
}
117126
@@ -143,9 +152,15 @@ class $dartInstanceManagerClassName {
143152
/// it was removed. Returns `null` if [identifier] was not associated with
144153
/// any strong reference.
145154
///
146-
/// This does not remove the weak referenced instance associated with
147-
/// [identifier]. This can be done with [removeWeakReference].
155+
/// Throws an `AssertionError` if the weak referenced instance associated with
156+
/// [identifier] is not removed first. This can be done with
157+
/// [removeWeakReference].
148158
T? remove<T extends $proxyApiBaseClassName>(int identifier) {
159+
final T? instance = _weakInstances[identifier]?.target as T?;
160+
assert(
161+
instance == null,
162+
'A strong instance with identifier \$identifier is being removed despite the weak reference still existing: \$instance',
163+
);
149164
return _strongInstances.remove(identifier) as T?;
150165
}
151166
@@ -191,24 +206,13 @@ class $dartInstanceManagerClassName {
191206
///
192207
/// Throws assertion error if the instance or its identifier has already been
193208
/// added.
194-
///
195-
/// Returns unique identifier of the [instance] added.
196209
void addHostCreatedInstance($proxyApiBaseClassName instance, int identifier) {
197-
_addInstanceWithIdentifier(instance, identifier);
198-
}
199-
200-
void _addInstanceWithIdentifier($proxyApiBaseClassName instance, int identifier) {
201210
assert(!containsIdentifier(identifier));
202211
assert(getIdentifier(instance) == null);
203212
assert(identifier >= 0);
204213
205214
_identifiers[instance] = identifier;
206-
_weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(instance);
207-
_finalizer.attach(instance, identifier, detach: instance);
208-
209-
final $proxyApiBaseClassName copy = instance.${classMemberNamePrefix}copy();
210-
_identifiers[copy] = identifier;
211-
_strongInstances[identifier] = copy;
215+
_strongInstances[identifier] = instance;
212216
}
213217
214218
/// Whether this manager contains the given [identifier].

packages/pigeon/lib/src/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import 'generator.dart';
1515
/// The current version of pigeon.
1616
///
1717
/// This must match the version in pubspec.yaml.
18-
const String pigeonVersion = '25.3.1';
18+
const String pigeonVersion = '25.3.2';
1919

2020
/// Read all the content from [stdin] to a String.
2121
String readStdin() {

packages/pigeon/lib/src/swift/templates.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ protocol ${instanceManagerFinalizerDelegateName(options)}: AnyObject {
3737
String instanceManagerFinalizerTemplate(InternalSwiftOptions options) => '''
3838
// Attaches to an object to receive a callback when the object is deallocated.
3939
internal final class ${_instanceManagerFinalizerName(options)} {
40-
private static let associatedObjectKey = malloc(1)!
40+
internal static let associatedObjectKey = malloc(1)!
4141
4242
private let identifier: Int64
4343
// Reference to the delegate is weak because the callback should be ignored if the
4444
// `InstanceManager` is deallocated.
45-
private weak var delegate: ${instanceManagerFinalizerDelegateName(options)}?
45+
internal weak var delegate: ${instanceManagerFinalizerDelegateName(options)}?
4646
4747
private init(identifier: Int64, delegate: ${instanceManagerFinalizerDelegateName(options)}) {
4848
self.identifier = identifier
@@ -57,7 +57,11 @@ internal final class ${_instanceManagerFinalizerName(options)} {
5757
}
5858
5959
static func detach(from instance: AnyObject) {
60-
objc_setAssociatedObject(instance, associatedObjectKey, nil, .OBJC_ASSOCIATION_ASSIGN)
60+
let finalizer = objc_getAssociatedObject(instance, associatedObjectKey) as? ${_instanceManagerFinalizerName(options)}
61+
if let finalizer = finalizer {
62+
finalizer.delegate = nil
63+
objc_setAssociatedObject(instance, associatedObjectKey, nil, .OBJC_ASSOCIATION_ASSIGN)
64+
}
6165
}
6266
6367
deinit {
@@ -219,6 +223,10 @@ final class ${swiftInstanceManagerClassName(options)} {
219223
/// The manager will be empty after this call returns.
220224
func removeAllObjects() throws {
221225
lockQueue.sync {
226+
let weakInstancesEnumerator = weakInstances.objectEnumerator()!
227+
while let instance = weakInstancesEnumerator.nextObject() {
228+
${_instanceManagerFinalizerName(options)}.detach(from: instance as AnyObject)
229+
}
222230
identifiers.removeAllObjects()
223231
weakInstances.removeAllObjects()
224232
strongInstances.removeAllObjects()

packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/proxy_api_tests.gen.dart

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,17 @@ class PigeonInstanceManager {
158158
///
159159
/// Returns the randomly generated id of the [instance] added.
160160
int addDartCreatedInstance(PigeonInternalProxyApiBaseClass instance) {
161+
assert(getIdentifier(instance) == null);
162+
161163
final int identifier = _nextUniqueIdentifier();
162-
_addInstanceWithIdentifier(instance, identifier);
164+
_identifiers[instance] = identifier;
165+
_weakInstances[identifier] =
166+
WeakReference<PigeonInternalProxyApiBaseClass>(instance);
167+
_finalizer.attach(instance, identifier, detach: instance);
168+
169+
final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy();
170+
_identifiers[copy] = identifier;
171+
_strongInstances[identifier] = copy;
163172
return identifier;
164173
}
165174

@@ -191,9 +200,15 @@ class PigeonInstanceManager {
191200
/// it was removed. Returns `null` if [identifier] was not associated with
192201
/// any strong reference.
193202
///
194-
/// This does not remove the weak referenced instance associated with
195-
/// [identifier]. This can be done with [removeWeakReference].
203+
/// Throws an `AssertionError` if the weak referenced instance associated with
204+
/// [identifier] is not removed first. This can be done with
205+
/// [removeWeakReference].
196206
T? remove<T extends PigeonInternalProxyApiBaseClass>(int identifier) {
207+
final T? instance = _weakInstances[identifier]?.target as T?;
208+
assert(
209+
instance == null,
210+
'A strong instance with identifier $identifier is being removed despite the weak reference still existing: $instance',
211+
);
197212
return _strongInstances.remove(identifier) as T?;
198213
}
199214

@@ -244,27 +259,14 @@ class PigeonInstanceManager {
244259
///
245260
/// Throws assertion error if the instance or its identifier has already been
246261
/// added.
247-
///
248-
/// Returns unique identifier of the [instance] added.
249262
void addHostCreatedInstance(
250263
PigeonInternalProxyApiBaseClass instance, int identifier) {
251-
_addInstanceWithIdentifier(instance, identifier);
252-
}
253-
254-
void _addInstanceWithIdentifier(
255-
PigeonInternalProxyApiBaseClass instance, int identifier) {
256264
assert(!containsIdentifier(identifier));
257265
assert(getIdentifier(instance) == null);
258266
assert(identifier >= 0);
259267

260268
_identifiers[instance] = identifier;
261-
_weakInstances[identifier] =
262-
WeakReference<PigeonInternalProxyApiBaseClass>(instance);
263-
_finalizer.attach(instance, identifier, detach: instance);
264-
265-
final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy();
266-
_identifiers[copy] = identifier;
267-
_strongInstances[identifier] = copy;
269+
_strongInstances[identifier] = instance;
268270
}
269271

270272
/// Whether this manager contains the given [identifier].

packages/pigeon/platform_tests/shared_test_plugin_code/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ dependencies:
1919
integration_test:
2020
sdk: flutter
2121
mockito: ^5.4.4
22+
23+
dev_dependencies:
24+
leak_tracker: any

packages/pigeon/platform_tests/shared_test_plugin_code/test/instance_manager_test.dart

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// This file specifically tests the test PigeonInstanceManager generated by core_tests.
66

77
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:leak_tracker/leak_tracker.dart';
89
import 'package:shared_test_plugin_code/src/generated/proxy_api_tests.gen.dart';
910

1011
void main() {
@@ -22,7 +23,7 @@ void main() {
2223
expect(instanceManager.getIdentifier(object), 0);
2324
expect(
2425
instanceManager.getInstanceWithWeakReference(0),
25-
object,
26+
isA<CopyableObject>(),
2627
);
2728
});
2829

@@ -106,7 +107,7 @@ void main() {
106107
expect(identical(object, copy), isFalse);
107108
});
108109

109-
test('removeStrongReference', () {
110+
test('remove', () {
110111
final PigeonInstanceManager instanceManager =
111112
PigeonInstanceManager(onWeakReferenceRemoved: (_) {});
112113

@@ -120,20 +121,16 @@ void main() {
120121
expect(instanceManager.containsIdentifier(0), isFalse);
121122
});
122123

123-
test('removeStrongReference removes only strong reference', () {
124+
test('remove throws AssertionError if weak reference still exists', () {
124125
final PigeonInstanceManager instanceManager =
125126
PigeonInstanceManager(onWeakReferenceRemoved: (_) {});
126127

127128
final CopyableObject object = CopyableObject(
128129
pigeon_instanceManager: instanceManager,
129130
);
130131

131-
instanceManager.addHostCreatedInstance(object, 0);
132-
expect(instanceManager.remove(0), isA<CopyableObject>());
133-
expect(
134-
instanceManager.getInstanceWithWeakReference(0),
135-
object,
136-
);
132+
instanceManager.addDartCreatedInstance(object);
133+
expect(() => instanceManager.remove(0), throwsAssertionError);
137134
});
138135

139136
test('getInstance can add a new weak reference', () {
@@ -153,6 +150,48 @@ void main() {
153150
)!;
154151
expect(identical(object, newWeakCopy), isFalse);
155152
});
153+
154+
test('addDartCreatedInstance should add finalizer to original object',
155+
() async {
156+
bool weakReferencedRemovedCalled = false;
157+
final PigeonInstanceManager instanceManager = PigeonInstanceManager(
158+
onWeakReferenceRemoved: (_) {
159+
weakReferencedRemovedCalled = true;
160+
},
161+
);
162+
163+
CopyableObject? object = CopyableObject(
164+
pigeon_instanceManager: instanceManager,
165+
);
166+
167+
instanceManager.addDartCreatedInstance(object);
168+
169+
object = null;
170+
await forceGC();
171+
172+
expect(weakReferencedRemovedCalled, isTrue);
173+
});
174+
175+
test('addHostCreatedInstance should not add finalizer to original object',
176+
() async {
177+
bool weakReferencedRemovedCalled = false;
178+
final PigeonInstanceManager instanceManager = PigeonInstanceManager(
179+
onWeakReferenceRemoved: (_) {
180+
weakReferencedRemovedCalled = true;
181+
},
182+
);
183+
184+
CopyableObject? object = CopyableObject(
185+
pigeon_instanceManager: instanceManager,
186+
);
187+
188+
instanceManager.addHostCreatedInstance(object, 0);
189+
190+
object = null;
191+
await forceGC();
192+
193+
expect(weakReferencedRemovedCalled, isFalse);
194+
});
156195
});
157196
}
158197

packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,35 @@ class InstanceManagerTest {
120120
assertTrue(instanceManager.containsInstance(instance))
121121
}
122122

123+
@Test
124+
fun clearPreventsFinalizationOfWeakInstances() {
125+
var finalizerRan = false
126+
val instanceManager: ProxyApiTestsPigeonInstanceManager =
127+
ProxyApiTestsPigeonInstanceManager.create(
128+
object : ProxyApiTestsPigeonInstanceManager.PigeonFinalizationListener {
129+
override fun onFinalize(identifier: Long) {
130+
finalizerRan = true
131+
}
132+
})
133+
134+
var testObject: Any? = Any()
135+
instanceManager.addDartCreatedInstance(testObject!!, 0)
136+
instanceManager.remove<Any?>(0)
137+
instanceManager.clear()
138+
139+
// To allow for object to be garbage collected.
140+
@Suppress("UNUSED_VALUE")
141+
testObject = null
142+
Runtime.getRuntime().gc()
143+
144+
// Changing this value triggers the callback.
145+
instanceManager.clearFinalizedWeakReferencesInterval = 1000
146+
instanceManager.stopFinalizationListener()
147+
148+
assertNull(instanceManager.getInstance<Any?>(0))
149+
assertFalse(finalizerRan)
150+
}
151+
123152
private fun createInstanceManager(): ProxyApiTestsPigeonInstanceManager {
124153
return ProxyApiTestsPigeonInstanceManager.create(
125154
object : ProxyApiTestsPigeonInstanceManager.PigeonFinalizationListener {

packages/pigeon/platform_tests/test_plugin/darwin/test_plugin/Sources/test_plugin/ProxyApiTests.gen.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ protocol ProxyApiTestsPigeonInternalFinalizerDelegate: AnyObject {
8282

8383
// Attaches to an object to receive a callback when the object is deallocated.
8484
internal final class ProxyApiTestsPigeonInternalFinalizer {
85-
private static let associatedObjectKey = malloc(1)!
85+
internal static let associatedObjectKey = malloc(1)!
8686

8787
private let identifier: Int64
8888
// Reference to the delegate is weak because the callback should be ignored if the
8989
// `InstanceManager` is deallocated.
90-
private weak var delegate: ProxyApiTestsPigeonInternalFinalizerDelegate?
90+
internal weak var delegate: ProxyApiTestsPigeonInternalFinalizerDelegate?
9191

9292
private init(identifier: Int64, delegate: ProxyApiTestsPigeonInternalFinalizerDelegate) {
9393
self.identifier = identifier
@@ -103,7 +103,13 @@ internal final class ProxyApiTestsPigeonInternalFinalizer {
103103
}
104104

105105
static func detach(from instance: AnyObject) {
106-
objc_setAssociatedObject(instance, associatedObjectKey, nil, .OBJC_ASSOCIATION_ASSIGN)
106+
let finalizer =
107+
objc_getAssociatedObject(instance, associatedObjectKey)
108+
as? ProxyApiTestsPigeonInternalFinalizer
109+
if let finalizer = finalizer {
110+
finalizer.delegate = nil
111+
objc_setAssociatedObject(instance, associatedObjectKey, nil, .OBJC_ASSOCIATION_ASSIGN)
112+
}
107113
}
108114

109115
deinit {
@@ -261,6 +267,10 @@ final class ProxyApiTestsPigeonInstanceManager {
261267
/// The manager will be empty after this call returns.
262268
func removeAllObjects() throws {
263269
lockQueue.sync {
270+
let weakInstancesEnumerator = weakInstances.objectEnumerator()!
271+
while let instance = weakInstancesEnumerator.nextObject() {
272+
ProxyApiTestsPigeonInternalFinalizer.detach(from: instance as AnyObject)
273+
}
264274
identifiers.removeAllObjects()
265275
weakInstances.removeAllObjects()
266276
strongInstances.removeAllObjects()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy