Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 25.3.2

* [dart] Fixes null pointer crashes/exceptions caused by premature finalization of Dart instances
for ProxyApis.

## 25.3.1

* [kotlin] Fixes Kotlin InstanceManager not properly removing callbacks from handler.
Expand Down
34 changes: 19 additions & 15 deletions packages/pigeon/lib/src/dart/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,17 @@ class $dartInstanceManagerClassName {
///
/// Returns the randomly generated id of the [instance] added.
int addDartCreatedInstance($proxyApiBaseClassName instance) {
assert(getIdentifier(instance) == null);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small feedback: adding a msg to this assert

Copy link
Contributor Author

@bparrishMines bparrishMines May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is usually my preference that adding a message is dependent on whether the assertion is self explanatory to the person who uses the method. InstanceManager.addDartCreatedInstance should only ever be called by the generated code, so I assume a maintainer of pigeon would understand this error without a message. While a user of pigeon would not get much value from an error message that states the InstanceManager already contains the instance.

But I'm biased since I wrote this, so I could be convinced if the intention of the check is not immediately clear to a new maintainer.


final int identifier = _nextUniqueIdentifier();
_addInstanceWithIdentifier(instance, identifier);
_identifiers[instance] = identifier;
_weakInstances[identifier] =
WeakReference<$proxyApiBaseClassName>(instance);
_finalizer.attach(instance, identifier, detach: instance);

final $proxyApiBaseClassName copy = instance.pigeon_copy();
_identifiers[copy] = identifier;
_strongInstances[identifier] = copy;
return identifier;
}

Expand Down Expand Up @@ -143,9 +152,15 @@ class $dartInstanceManagerClassName {
/// it was removed. Returns `null` if [identifier] was not associated with
/// any strong reference.
///
/// This does not remove the weak referenced instance associated with
/// [identifier]. This can be done with [removeWeakReference].
/// Throws an `AssertionError` if the weak referenced instance associated with
/// [identifier] is not removed first. This can be done with
/// [removeWeakReference].
T? remove<T extends $proxyApiBaseClassName>(int identifier) {
final T? instance = _weakInstances[identifier]?.target as T?;
assert(
instance == null,
'A strong instance with identifier \$identifier is being removed despite the weak reference still existing: \$instance',
);
return _strongInstances.remove(identifier) as T?;
}

Expand Down Expand Up @@ -191,24 +206,13 @@ class $dartInstanceManagerClassName {
///
/// Throws assertion error if the instance or its identifier has already been
/// added.
///
/// Returns unique identifier of the [instance] added.
void addHostCreatedInstance($proxyApiBaseClassName instance, int identifier) {
_addInstanceWithIdentifier(instance, identifier);
}

void _addInstanceWithIdentifier($proxyApiBaseClassName instance, int identifier) {
assert(!containsIdentifier(identifier));
assert(getIdentifier(instance) == null);
assert(identifier >= 0);

_identifiers[instance] = identifier;
_weakInstances[identifier] = WeakReference<$proxyApiBaseClassName>(instance);
_finalizer.attach(instance, identifier, detach: instance);

final $proxyApiBaseClassName copy = instance.${classMemberNamePrefix}copy();
_identifiers[copy] = identifier;
_strongInstances[identifier] = copy;
_strongInstances[identifier] = instance;
}

/// Whether this manager contains the given [identifier].
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'generator.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '25.3.1';
const String pigeonVersion = '25.3.2';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,17 @@ class PigeonInstanceManager {
///
/// Returns the randomly generated id of the [instance] added.
int addDartCreatedInstance(PigeonInternalProxyApiBaseClass instance) {
assert(getIdentifier(instance) == null);

final int identifier = _nextUniqueIdentifier();
_addInstanceWithIdentifier(instance, identifier);
_identifiers[instance] = identifier;
_weakInstances[identifier] =
WeakReference<PigeonInternalProxyApiBaseClass>(instance);
_finalizer.attach(instance, identifier, detach: instance);

final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy();
_identifiers[copy] = identifier;
_strongInstances[identifier] = copy;
return identifier;
}

Expand Down Expand Up @@ -191,9 +200,15 @@ class PigeonInstanceManager {
/// it was removed. Returns `null` if [identifier] was not associated with
/// any strong reference.
///
/// This does not remove the weak referenced instance associated with
/// [identifier]. This can be done with [removeWeakReference].
/// Throws an `AssertionError` if the weak referenced instance associated with
/// [identifier] is not removed first. This can be done with
/// [removeWeakReference].
T? remove<T extends PigeonInternalProxyApiBaseClass>(int identifier) {
final T? instance = _weakInstances[identifier]?.target as T?;
assert(
instance == null,
'A strong instance with identifier $identifier is being removed despite the weak reference still existing: $instance',
);
return _strongInstances.remove(identifier) as T?;
}

Expand Down Expand Up @@ -244,27 +259,14 @@ class PigeonInstanceManager {
///
/// Throws assertion error if the instance or its identifier has already been
/// added.
///
/// Returns unique identifier of the [instance] added.
void addHostCreatedInstance(
PigeonInternalProxyApiBaseClass instance, int identifier) {
_addInstanceWithIdentifier(instance, identifier);
}

void _addInstanceWithIdentifier(
PigeonInternalProxyApiBaseClass instance, int identifier) {
assert(!containsIdentifier(identifier));
assert(getIdentifier(instance) == null);
assert(identifier >= 0);

_identifiers[instance] = identifier;
_weakInstances[identifier] =
WeakReference<PigeonInternalProxyApiBaseClass>(instance);
_finalizer.attach(instance, identifier, detach: instance);

final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy();
_identifiers[copy] = identifier;
_strongInstances[identifier] = copy;
_strongInstances[identifier] = instance;
}

/// Whether this manager contains the given [identifier].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ dependencies:
integration_test:
sdk: flutter
mockito: ^5.4.4

dev_dependencies:
leak_tracker: any
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// This file specifically tests the test PigeonInstanceManager generated by core_tests.

import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker/leak_tracker.dart';
import 'package:shared_test_plugin_code/src/generated/proxy_api_tests.gen.dart';

void main() {
Expand All @@ -22,7 +23,7 @@ void main() {
expect(instanceManager.getIdentifier(object), 0);
expect(
instanceManager.getInstanceWithWeakReference(0),
object,
isA<CopyableObject>(),
);
});

Expand Down Expand Up @@ -106,7 +107,7 @@ void main() {
expect(identical(object, copy), isFalse);
});

test('removeStrongReference', () {
test('remove', () {
final PigeonInstanceManager instanceManager =
PigeonInstanceManager(onWeakReferenceRemoved: (_) {});

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

test('removeStrongReference removes only strong reference', () {
test('remove throws AssertionError is weak reference still exists', () {
final PigeonInstanceManager instanceManager =
PigeonInstanceManager(onWeakReferenceRemoved: (_) {});

final CopyableObject object = CopyableObject(
pigeon_instanceManager: instanceManager,
);

instanceManager.addHostCreatedInstance(object, 0);
expect(instanceManager.remove(0), isA<CopyableObject>());
expect(
instanceManager.getInstanceWithWeakReference(0),
object,
);
instanceManager.addDartCreatedInstance(object);
expect(() => instanceManager.remove(0), throwsAssertionError);
});

test('getInstance can add a new weak reference', () {
Expand All @@ -153,6 +150,48 @@ void main() {
)!;
expect(identical(object, newWeakCopy), isFalse);
});

test('addDartCreatedInstance should add finalizer to original object',
() async {
bool weakReferencedRemovedCalled = false;
final PigeonInstanceManager instanceManager = PigeonInstanceManager(
onWeakReferenceRemoved: (_) {
weakReferencedRemovedCalled = true;
},
);

CopyableObject? object = CopyableObject(
pigeon_instanceManager: instanceManager,
);

instanceManager.addDartCreatedInstance(object);

object = null;
await forceGC();

expect(weakReferencedRemovedCalled, isTrue);
});

test('addHostCreatedInstance should not add finalizer to original object',
() async {
bool weakReferencedRemovedCalled = false;
final PigeonInstanceManager instanceManager = PigeonInstanceManager(
onWeakReferenceRemoved: (_) {
weakReferencedRemovedCalled = true;
},
);

CopyableObject? object = CopyableObject(
pigeon_instanceManager: instanceManager,
);

instanceManager.addHostCreatedInstance(object, 0);

object = null;
await forceGC();

expect(weakReferencedRemovedCalled, isFalse);
});
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: pigeon
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22
version: 25.3.1 # This must match the version in lib/src/generator_tools.dart
version: 25.3.2 # This must match the version in lib/src/generator_tools.dart

environment:
sdk: ^3.4.0
Expand Down
1 change: 1 addition & 0 deletions script/configs/allowed_unpinned_deps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
- io
- js
- json_serializable
- leak_tracker
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to use forceGC

- leak_tracker_flutter_testing
- lints
- logging
Expand Down