From c828fff65b004d0135e70e526f08d641578a3945 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:31:38 -0400 Subject: [PATCH 01/10] override fix --- packages/pigeon/lib/src/kotlin/templates.dart | 2 +- .../example/test_plugin/ProxyApiTests.gen.kt | 2 +- .../example/test_plugin/InstanceManagerTest.kt | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index 652a0f683af..e7b95e17eb0 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -113,7 +113,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene fun getIdentifierForStrongReference(instance: Any?): Long? { logWarningIfFinalizationListenerHasStopped() val identifier = identifiers[instance] - if (identifier != null) { + if (identifier != null && strongInstances[identifier] == null) { strongInstances[identifier] = instance!! } return identifier diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 3b1933827d6..08e58f1c34f 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -145,7 +145,7 @@ class ProxyApiTestsPigeonInstanceManager( fun getIdentifierForStrongReference(instance: Any?): Long? { logWarningIfFinalizationListenerHasStopped() val identifier = identifiers[instance] - if (identifier != null) { + if (identifier != null && strongInstances[identifier] == null) { strongInstances[identifier] = instance!! } return identifier diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt index 788adc19f0d..86cba7f999a 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt @@ -149,10 +149,28 @@ class InstanceManagerTest { assertFalse(finalizerRan) } + @Test + fun getIdentifierForStrongReferenceDoesNotReplaceOriginal() { + val instanceManager: ProxyApiTestsPigeonInstanceManager = createInstanceManager() + instanceManager.stopFinalizationListener() + + val testString = "aString" + val testObject1 = TestDataClass(testString) + + val identifier = instanceManager.addHostCreatedInstance(testObject1) + + val testObject2 = TestDataClass(testString) + assertTrue(instanceManager.containsInstance(testObject2)) + assertEquals(identifier, instanceManager.getIdentifierForStrongReference(testObject2)) + assertTrue(testObject1 === instanceManager.remove(identifier)) + } + private fun createInstanceManager(): ProxyApiTestsPigeonInstanceManager { return ProxyApiTestsPigeonInstanceManager.create( object : ProxyApiTestsPigeonInstanceManager.PigeonFinalizationListener { override fun onFinalize(identifier: Long) {} }) } + + data class TestDataClass(val value: String) } From 3300041c838ef5d89af612696f8afed7bab08159 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 19 Sep 2025 14:11:01 -0400 Subject: [PATCH 02/10] add log and version bump --- packages/pigeon/CHANGELOG.md | 4 ++- .../lib/src/kotlin/kotlin_generator.dart | 8 ++++- .../example/test_plugin/ProxyApiTests.gen.kt | 32 ++++++++++++++++--- packages/pigeon/pubspec.yaml | 2 +- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index d36975e5b0f..0bea707a6e1 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 26.0.2 +* [kotlin] Fixes support for classes that override equals and hashCode for ProxyApis. +* [kotlin] Adds error message log when a new Dart proxy instance fails to be created. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 26.0.1 diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart index 992b1b65971..17f579dbd6a 100644 --- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart +++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart @@ -1043,7 +1043,13 @@ if (wrapped == null) { indent.format(''' ${index > 0 ? ' else ' : ''}if (${versionCheck}value is $className) { - registrar.get$hostProxyApiPrefix${api.name}().${classMemberNamePrefix}newInstance(value) { } + registrar.get$hostProxyApiPrefix${api.name}().${classMemberNamePrefix}newInstance(value) { + if (it.isFailure) { + Log.w( + "${proxyApiCodecName(const InternalKotlinOptions(kotlinOut: ''))}", + "Failed to create new Dart proxy instance of ${api.name}: \$value. \${it.exceptionOrNull()}") + } + } }'''); }); indent.newln(); diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 08e58f1c34f..61f50c86123 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -461,13 +461,37 @@ private class ProxyApiTestsPigeonProxyApiBaseCodec( } if (value is ProxyApiTestClass) { - registrar.getPigeonApiProxyApiTestClass().pigeon_newInstance(value) {} + registrar.getPigeonApiProxyApiTestClass().pigeon_newInstance(value) { + if (it.isFailure) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of ProxyApiTestClass: $value. ${it.exceptionOrNull()}") + } + } } else if (value is com.example.test_plugin.ProxyApiSuperClass) { - registrar.getPigeonApiProxyApiSuperClass().pigeon_newInstance(value) {} + registrar.getPigeonApiProxyApiSuperClass().pigeon_newInstance(value) { + if (it.isFailure) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of ProxyApiSuperClass: $value. ${it.exceptionOrNull()}") + } + } } else if (value is ProxyApiInterface) { - registrar.getPigeonApiProxyApiInterface().pigeon_newInstance(value) {} + registrar.getPigeonApiProxyApiInterface().pigeon_newInstance(value) { + if (it.isFailure) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of ProxyApiInterface: $value. ${it.exceptionOrNull()}") + } + } } else if (android.os.Build.VERSION.SDK_INT >= 25 && value is ClassWithApiRequirement) { - registrar.getPigeonApiClassWithApiRequirement().pigeon_newInstance(value) {} + registrar.getPigeonApiClassWithApiRequirement().pigeon_newInstance(value) { + if (it.isFailure) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of ClassWithApiRequirement: $value. ${it.exceptionOrNull()}") + } + } } when { diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 3197a83dd4e..3e247f4cfad 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -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: 26.0.1 # This must match the version in lib/src/generator_tools.dart +version: 26.0.2 # This must match the version in lib/src/generator_tools.dart environment: sdk: ^3.7.0 From 6d21fdd1e5a001e3767d797e35c3493f216b0b54 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:56:48 -0400 Subject: [PATCH 03/10] better solution for equality --- packages/pigeon/lib/src/kotlin/templates.dart | 33 +++++++++++++---- .../example/test_plugin/ProxyApiTests.gen.kt | 35 ++++++++++++++---- .../test_plugin/InstanceManagerTest.kt | 36 ++++++++++++++++--- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index e7b95e17eb0..de4b8f89639 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -42,7 +42,25 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene fun onFinalize(identifier: Long) } - private val identifiers = java.util.WeakHashMap() + // Wraps an instance in a class that overrides the `equals` and `hashCode` methods using identity + // rather than equality. + private class IdentityKey { + private val instance: java.lang.ref.WeakReference + + constructor(instance: T) { + this.instance = java.lang.ref.WeakReference(instance) + } + + override fun equals(other: Any?): Boolean { + return instance.get() != null && other is IdentityKey<*> && other.instance.get() === instance.get() + } + + override fun hashCode(): Int { + return instance.get().hashCode() + } + } + + private val identifiers = java.util.WeakHashMap, Long>() private val weakInstances = HashMap>() private val strongInstances = HashMap() private val referenceQueue = java.lang.ref.ReferenceQueue() @@ -112,9 +130,12 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene */ fun getIdentifierForStrongReference(instance: Any?): Long? { logWarningIfFinalizationListenerHasStopped() - val identifier = identifiers[instance] - if (identifier != null && strongInstances[identifier] == null) { - strongInstances[identifier] = instance!! + if (instance == null) { + return null + } + val identifier = identifiers[IdentityKey(instance)] + if (identifier != null) { + strongInstances[identifier] = instance } return identifier } @@ -158,7 +179,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene /** Returns whether this manager contains the given `instance`. */ fun containsInstance(instance: Any?): Boolean { logWarningIfFinalizationListenerHasStopped() - return identifiers.containsKey(instance) + return instance != null && identifiers.containsKey(IdentityKey(instance)) } /** @@ -217,7 +238,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene "Identifier has already been added: \$identifier" } val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) - identifiers[instance] = identifier + identifiers[IdentityKey(instance)] = identifier weakInstances[identifier] = weakReference weakReferencesToIdentifiers[weakReference] = identifier strongInstances[identifier] = instance diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 61f50c86123..863dff068d3 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -75,7 +75,27 @@ class ProxyApiTestsPigeonInstanceManager( fun onFinalize(identifier: Long) } - private val identifiers = java.util.WeakHashMap() + // Wraps an instance in a class that overrides the `equals` and `hashCode` methods using identity + // rather than equality. + private class IdentityKey { + private val instance: java.lang.ref.WeakReference + + constructor(instance: T) { + this.instance = java.lang.ref.WeakReference(instance) + } + + override fun equals(other: Any?): Boolean { + return instance.get() != null && + other is IdentityKey<*> && + other.instance.get() === instance.get() + } + + override fun hashCode(): Int { + return instance.get().hashCode() + } + } + + private val identifiers = java.util.WeakHashMap, Long>() private val weakInstances = HashMap>() private val strongInstances = HashMap() private val referenceQueue = java.lang.ref.ReferenceQueue() @@ -144,9 +164,12 @@ class ProxyApiTestsPigeonInstanceManager( */ fun getIdentifierForStrongReference(instance: Any?): Long? { logWarningIfFinalizationListenerHasStopped() - val identifier = identifiers[instance] - if (identifier != null && strongInstances[identifier] == null) { - strongInstances[identifier] = instance!! + if (instance == null) { + return null + } + val identifier = identifiers[IdentityKey(instance)] + if (identifier != null) { + strongInstances[identifier] = instance } return identifier } @@ -192,7 +215,7 @@ class ProxyApiTestsPigeonInstanceManager( /** Returns whether this manager contains the given `instance`. */ fun containsInstance(instance: Any?): Boolean { logWarningIfFinalizationListenerHasStopped() - return identifiers.containsKey(instance) + return instance != null && identifiers.containsKey(IdentityKey(instance)) } /** @@ -252,7 +275,7 @@ class ProxyApiTestsPigeonInstanceManager( "Identifier has already been added: $identifier" } val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) - identifiers[instance] = identifier + identifiers[IdentityKey(instance)] = identifier weakInstances[identifier] = weakReference weakReferencesToIdentifiers[weakReference] = identifier strongInstances[identifier] = instance diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt index 86cba7f999a..f3c91215fbb 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt @@ -150,19 +150,45 @@ class InstanceManagerTest { } @Test - fun getIdentifierForStrongReferenceDoesNotReplaceOriginal() { + fun containsInstanceAndGetIdentifierForStrongReferenceUseIdentityComparison() { val instanceManager: ProxyApiTestsPigeonInstanceManager = createInstanceManager() instanceManager.stopFinalizationListener() + // Create two objects that are equal. val testString = "aString" val testObject1 = TestDataClass(testString) + val testObject2 = TestDataClass(testString) + assertEquals(testObject1, testObject2) - val identifier = instanceManager.addHostCreatedInstance(testObject1) + val identifier1 = instanceManager.addHostCreatedInstance(testObject1) + assertFalse(instanceManager.containsInstance(testObject2)) + assertNull(instanceManager.getIdentifierForStrongReference(testObject2)) - val testObject2 = TestDataClass(testString) + val identifier2 = instanceManager.addHostCreatedInstance(testObject2) + assertTrue(instanceManager.containsInstance(testObject1)) assertTrue(instanceManager.containsInstance(testObject2)) - assertEquals(identifier, instanceManager.getIdentifierForStrongReference(testObject2)) - assertTrue(testObject1 === instanceManager.remove(identifier)) + assertEquals(identifier1, instanceManager.getIdentifierForStrongReference(testObject1)) + assertEquals(identifier2, instanceManager.getIdentifierForStrongReference(testObject2)) + } + + @Test + fun addingTwoDartCreatedInstancesThatAreEqual() { + val instanceManager: ProxyApiTestsPigeonInstanceManager = createInstanceManager() + instanceManager.stopFinalizationListener() + + // Create two objects that are equal. + val testString = "aString" + val testObject1 = TestDataClass(testString) + val testObject2 = TestDataClass(testString) + assertEquals(testObject1, testObject2) + + instanceManager.addDartCreatedInstance(testObject1, 0) + instanceManager.addDartCreatedInstance(testObject2, 1) + + assertEquals(testObject1, instanceManager.getInstance(0)) + assertEquals(testObject2, instanceManager.getInstance(1)) + assertEquals(0L, instanceManager.getIdentifierForStrongReference(testObject1)) + assertEquals(1L, instanceManager.getIdentifierForStrongReference(testObject2)) } private fun createInstanceManager(): ProxyApiTestsPigeonInstanceManager { From b4fa524b022c3587d2bd8cab9a8f2ff4777f9062 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Sun, 21 Sep 2025 14:39:02 -0400 Subject: [PATCH 04/10] fix pigeonVersion --- packages/pigeon/lib/src/generator_tools.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index 169d0eb6309..570333937e0 100644 --- a/packages/pigeon/lib/src/generator_tools.dart +++ b/packages/pigeon/lib/src/generator_tools.dart @@ -15,7 +15,7 @@ import 'generator.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '26.0.1'; +const String pigeonVersion = '26.0.2'; /// Read all the content from [stdin] to a String. String readStdin() { From 3508c817243f14cf20ac4d1467d17776a7b0ed6c Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Sun, 21 Sep 2025 15:38:45 -0400 Subject: [PATCH 05/10] switch to identity hash code --- packages/pigeon/lib/src/kotlin/templates.dart | 2 +- .../main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index de4b8f89639..d6fee758ca8 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -56,7 +56,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene } override fun hashCode(): Int { - return instance.get().hashCode() + return System.identityHashCode(instance.get()) } } diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 863dff068d3..ed6a2781657 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -91,7 +91,7 @@ class ProxyApiTestsPigeonInstanceManager( } override fun hashCode(): Int { - return instance.get().hashCode() + return System.identityHashCode(instance.get()) } } From 39db552dfa4362e028ac034c9e99d02794a9a9de Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:19:33 -0400 Subject: [PATCH 06/10] use a shared weakreference object --- packages/pigeon/lib/src/kotlin/templates.dart | 38 +++++++++--------- .../example/test_plugin/ProxyApiTests.gen.kt | 39 ++++++++----------- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index 513f336d9dd..68418e61fac 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -42,29 +42,29 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene fun onFinalize(identifier: Long) } - // Wraps an instance in a class that overrides the `equals` and `hashCode` methods using identity - // rather than equality. - private class IdentityKey { - private val instance: java.lang.ref.WeakReference + // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather + // than equality. + private class IdentityWeakReference: java.lang.ref.WeakReference { + constructor(instance: T) : super(instance) - constructor(instance: T) { - this.instance = java.lang.ref.WeakReference(instance) - } + constructor(instance: T, queue: java.lang.ref.ReferenceQueue) : super(instance, queue) override fun equals(other: Any?): Boolean { - return instance.get() != null && other is IdentityKey<*> && other.instance.get() === instance.get() + return get() != null && + other is IdentityWeakReference<*> && + other.get() === get() } override fun hashCode(): Int { - return System.identityHashCode(instance.get()) + return System.identityHashCode(get()) } } - private val identifiers = java.util.WeakHashMap, Long>() - private val weakInstances = HashMap>() + private val identifiers = java.util.WeakHashMap, Long>() + private val weakInstances = HashMap>() private val strongInstances = HashMap() private val referenceQueue = java.lang.ref.ReferenceQueue() - private val weakReferencesToIdentifiers = HashMap, Long>() + private val weakReferencesToIdentifiers = HashMap, Long>() private val handler = android.os.Handler(android.os.Looper.getMainLooper()) private val releaseAllFinalizedInstancesRunnable = Runnable { this.releaseAllFinalizedInstances() @@ -133,7 +133,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene if (instance == null) { return null } - val identifier = identifiers[IdentityKey(instance)] + val identifier = identifiers[IdentityWeakReference(instance)] if (identifier != null) { strongInstances[identifier] = instance } @@ -172,14 +172,14 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene /** Retrieves the instance associated with identifier, if present, otherwise `null`. */ fun getInstance(identifier: Long): T? { logWarningIfFinalizationListenerHasStopped() - val instance = weakInstances[identifier] as java.lang.ref.WeakReference? + val instance = weakInstances[identifier] as IdentityWeakReference? return instance?.get() } /** Returns whether this manager contains the given `instance`. */ fun containsInstance(instance: Any?): Boolean { logWarningIfFinalizationListenerHasStopped() - return instance != null && identifiers.containsKey(IdentityKey(instance)) + return instance != null && identifiers.containsKey(IdentityWeakReference(instance)) } /** @@ -220,8 +220,8 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene if (hasFinalizationListenerStopped()) { return } - var reference: java.lang.ref.WeakReference? - while ((referenceQueue.poll() as java.lang.ref.WeakReference?).also { reference = it } != null) { + var reference: IdentityWeakReference? + while ((referenceQueue.poll() as IdentityWeakReference?).also { reference = it } != null) { val identifier = weakReferencesToIdentifiers.remove(reference) if (identifier != null) { weakInstances.remove(identifier) @@ -237,8 +237,8 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene require(!weakInstances.containsKey(identifier)) { "Identifier has already been added: \$identifier" } - val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) - identifiers[IdentityKey(instance)] = identifier + val weakReference = IdentityWeakReference(instance, referenceQueue) + identifiers[weakReference] = identifier weakInstances[identifier] = weakReference weakReferencesToIdentifiers[weakReference] = identifier strongInstances[identifier] = instance diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 491de328c13..436cab8a6f1 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -75,31 +75,27 @@ class ProxyApiTestsPigeonInstanceManager( fun onFinalize(identifier: Long) } - // Wraps an instance in a class that overrides the `equals` and `hashCode` methods using identity - // rather than equality. - private class IdentityKey { - private val instance: java.lang.ref.WeakReference + // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather + // than equality. + private class IdentityWeakReference : java.lang.ref.WeakReference { + constructor(instance: T) : super(instance) - constructor(instance: T) { - this.instance = java.lang.ref.WeakReference(instance) - } + constructor(instance: T, queue: java.lang.ref.ReferenceQueue) : super(instance, queue) override fun equals(other: Any?): Boolean { - return instance.get() != null && - other is IdentityKey<*> && - other.instance.get() === instance.get() + return get() != null && other is IdentityWeakReference<*> && other.get() === get() } override fun hashCode(): Int { - return System.identityHashCode(instance.get()) + return System.identityHashCode(get()) } } - private val identifiers = java.util.WeakHashMap, Long>() - private val weakInstances = HashMap>() + private val identifiers = java.util.WeakHashMap, Long>() + private val weakInstances = HashMap>() private val strongInstances = HashMap() private val referenceQueue = java.lang.ref.ReferenceQueue() - private val weakReferencesToIdentifiers = HashMap, Long>() + private val weakReferencesToIdentifiers = HashMap, Long>() private val handler = android.os.Handler(android.os.Looper.getMainLooper()) private val releaseAllFinalizedInstancesRunnable = Runnable { this.releaseAllFinalizedInstances() @@ -167,7 +163,7 @@ class ProxyApiTestsPigeonInstanceManager( if (instance == null) { return null } - val identifier = identifiers[IdentityKey(instance)] + val identifier = identifiers[IdentityWeakReference(instance)] if (identifier != null) { strongInstances[identifier] = instance } @@ -208,14 +204,14 @@ class ProxyApiTestsPigeonInstanceManager( /** Retrieves the instance associated with identifier, if present, otherwise `null`. */ fun getInstance(identifier: Long): T? { logWarningIfFinalizationListenerHasStopped() - val instance = weakInstances[identifier] as java.lang.ref.WeakReference? + val instance = weakInstances[identifier] as IdentityWeakReference? return instance?.get() } /** Returns whether this manager contains the given `instance`. */ fun containsInstance(instance: Any?): Boolean { logWarningIfFinalizationListenerHasStopped() - return instance != null && identifiers.containsKey(IdentityKey(instance)) + return instance != null && identifiers.containsKey(IdentityWeakReference(instance)) } /** @@ -256,9 +252,8 @@ class ProxyApiTestsPigeonInstanceManager( if (hasFinalizationListenerStopped()) { return } - var reference: java.lang.ref.WeakReference? - while ((referenceQueue.poll() as java.lang.ref.WeakReference?).also { reference = it } != - null) { + var reference: IdentityWeakReference? + while ((referenceQueue.poll() as IdentityWeakReference?).also { reference = it } != null) { val identifier = weakReferencesToIdentifiers.remove(reference) if (identifier != null) { weakInstances.remove(identifier) @@ -274,8 +269,8 @@ class ProxyApiTestsPigeonInstanceManager( require(!weakInstances.containsKey(identifier)) { "Identifier has already been added: $identifier" } - val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) - identifiers[IdentityKey(instance)] = identifier + val weakReference = IdentityWeakReference(instance, referenceQueue) + identifiers[weakReference] = identifier weakInstances[identifier] = weakReference weakReferencesToIdentifiers[weakReference] = identifier strongInstances[identifier] = instance From 2e7258fcbbef59aac15550e1e3de27e3b189eea5 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:37:21 -0400 Subject: [PATCH 07/10] resort to using the saved hashcode and using the actual instance --- packages/pigeon/lib/src/kotlin/templates.dart | 20 ++++++++++++------- .../example/test_plugin/ProxyApiTests.gen.kt | 16 +++++++++++---- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index 68418e61fac..7d0c9d74ca9 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -44,19 +44,25 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. - private class IdentityWeakReference: java.lang.ref.WeakReference { - constructor(instance: T) : super(instance) + private class IdentityWeakReference : java.lang.ref.WeakReference { + private val savedHashCode: Int - constructor(instance: T, queue: java.lang.ref.ReferenceQueue) : super(instance, queue) + constructor(instance: T) : this(instance, null) + + constructor(instance: T, queue: java.lang.ref.ReferenceQueue?) : super(instance, queue) { + savedHashCode = System.identityHashCode(instance) + } override fun equals(other: Any?): Boolean { - return get() != null && - other is IdentityWeakReference<*> && - other.get() === get() + val instance = get() + if (instance != null) { + return other is IdentityWeakReference<*> && other.get() === instance + } + return other === this } override fun hashCode(): Int { - return System.identityHashCode(get()) + return savedHashCode } } diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 436cab8a6f1..3da021033cf 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -78,16 +78,24 @@ class ProxyApiTestsPigeonInstanceManager( // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. private class IdentityWeakReference : java.lang.ref.WeakReference { - constructor(instance: T) : super(instance) + private val savedHashCode: Int - constructor(instance: T, queue: java.lang.ref.ReferenceQueue) : super(instance, queue) + constructor(instance: T) : this(instance, null) + + constructor(instance: T, queue: java.lang.ref.ReferenceQueue?) : super(instance, queue) { + savedHashCode = System.identityHashCode(instance) + } override fun equals(other: Any?): Boolean { - return get() != null && other is IdentityWeakReference<*> && other.get() === get() + val instance = get() + if (instance != null) { + return other is IdentityWeakReference<*> && other.get() === instance + } + return other === this } override fun hashCode(): Int { - return System.identityHashCode(get()) + return savedHashCode } } From 643f5477f0687f519734cb2724e0e9f7b3e63f4f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:47:30 -0400 Subject: [PATCH 08/10] add tests for class --- packages/pigeon/lib/src/kotlin/templates.dart | 2 +- .../example/test_plugin/ProxyApiTests.gen.kt | 2 +- .../test_plugin/InstanceManagerTest.kt | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index 7d0c9d74ca9..b4dd9f6c395 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -44,7 +44,7 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. - private class IdentityWeakReference : java.lang.ref.WeakReference { + class IdentityWeakReference : java.lang.ref.WeakReference { private val savedHashCode: Int constructor(instance: T) : this(instance, null) diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index 3da021033cf..d2f200476e3 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -77,7 +77,7 @@ class ProxyApiTestsPigeonInstanceManager( // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. - private class IdentityWeakReference : java.lang.ref.WeakReference { + class IdentityWeakReference : java.lang.ref.WeakReference { private val savedHashCode: Int constructor(instance: T) : this(instance, null) diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt index 46af9ebc3b9..69faaf769bf 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt @@ -191,6 +191,30 @@ class InstanceManagerTest { assertEquals(1L, instanceManager.getIdentifierForStrongReference(testObject2)) } + @Test + fun identityWeakReferenceAreEqualWithSameInstance() { + val testObject = Any() + + assertEquals( + ProxyApiTestsPigeonInstanceManager.IdentityWeakReference(testObject), + ProxyApiTestsPigeonInstanceManager.IdentityWeakReference(testObject)) + } + + @Test + fun identityWeakReferenceRemainsEqualAfterGetReturnsNull() { + var testObject: Any? = Any() + + val reference = ProxyApiTestsPigeonInstanceManager.IdentityWeakReference(testObject!!) + + // To allow for object to be garbage collected. + @Suppress("UNUSED_VALUE") + testObject = null + Runtime.getRuntime().gc() + + assertNull(reference.get()) + assertEquals(reference, reference) + } + private fun createInstanceManager(): ProxyApiTestsPigeonInstanceManager { return ProxyApiTestsPigeonInstanceManager.create( object : ProxyApiTestsPigeonInstanceManager.PigeonFinalizationListener { From e10b4058255b1276d6fab47f7ad270b11b170ff6 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:51:48 -0400 Subject: [PATCH 09/10] another test and more docs --- packages/pigeon/lib/src/kotlin/templates.dart | 4 ++++ .../example/test_plugin/ProxyApiTests.gen.kt | 4 ++++ .../test_plugin/InstanceManagerTest.kt | 21 ++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/pigeon/lib/src/kotlin/templates.dart b/packages/pigeon/lib/src/kotlin/templates.dart index b4dd9f6c395..731ec59d0af 100644 --- a/packages/pigeon/lib/src/kotlin/templates.dart +++ b/packages/pigeon/lib/src/kotlin/templates.dart @@ -44,6 +44,10 @@ class ${kotlinInstanceManagerClassName(options)}(private val finalizationListene // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. + // + // Two `IdentityWeakReference`s are equal if either + // 1: `get()` returns the identical nonnull value for both references. + // 2: `get()` returns null for both references and the references are identical. class IdentityWeakReference : java.lang.ref.WeakReference { private val savedHashCode: Int diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index d2f200476e3..b68ea2f3bcc 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -77,6 +77,10 @@ class ProxyApiTestsPigeonInstanceManager( // Extends WeakReference and overrides the `equals` and `hashCode` methods using identity rather // than equality. + // + // Two `IdentityWeakReference`s are equal if either + // 1: `get()` returns the identical nonnull value for both references. + // 2: `get()` returns null for both references and the references are identical. class IdentityWeakReference : java.lang.ref.WeakReference { private val savedHashCode: Int diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt index 69faaf769bf..b92638f1980 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/InstanceManagerTest.kt @@ -192,7 +192,7 @@ class InstanceManagerTest { } @Test - fun identityWeakReferenceAreEqualWithSameInstance() { + fun identityWeakReferencesAreEqualWithSameInstance() { val testObject = Any() assertEquals( @@ -215,6 +215,25 @@ class InstanceManagerTest { assertEquals(reference, reference) } + @Test + fun identityWeakReferencesAreNotEqualAfterGetReturnsNull() { + var testObject1: Any? = Any() + var testObject2: Any? = Any() + + val reference1 = ProxyApiTestsPigeonInstanceManager.IdentityWeakReference(testObject1!!) + val reference2 = ProxyApiTestsPigeonInstanceManager.IdentityWeakReference(testObject2!!) + + // To allow for object to be garbage collected. + @Suppress("UNUSED_VALUE") + testObject1 = null + testObject2 = null + Runtime.getRuntime().gc() + + assertNull(reference1.get()) + assertNull(reference2.get()) + assertFalse(reference1 == reference2) + } + private fun createInstanceManager(): ProxyApiTestsPigeonInstanceManager { return ProxyApiTestsPigeonInstanceManager.create( object : ProxyApiTestsPigeonInstanceManager.PigeonFinalizationListener { From f33dd17dd96420aa53a0a9c78b1d52c4adbdbb3e Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:08:57 -0400 Subject: [PATCH 10/10] create a local method for logging --- .../lib/src/kotlin/kotlin_generator.dart | 13 ++++++++--- .../example/test_plugin/ProxyApiTests.gen.kt | 22 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart index be42e7e3ae0..7762d1c1de8 100644 --- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart +++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart @@ -1031,6 +1031,15 @@ if (wrapped == null) { }); indent.newln(); + indent.format(''' + fun logNewInstanceFailure(apiName: String, value: Any, exception: Throwable?) { + Log.w( + "${proxyApiCodecName(const InternalKotlinOptions(kotlinOut: ''))}", + "Failed to create new Dart proxy instance of \$apiName: \$value. \$exception" + ) + } + '''); + enumerate(sortedApis, (int index, AstProxyApi api) { final String className = api.kotlinOptions?.fullClassName ?? api.name; @@ -1045,9 +1054,7 @@ if (wrapped == null) { ${index > 0 ? ' else ' : ''}if (${versionCheck}value is $className) { registrar.get$hostProxyApiPrefix${api.name}().${classMemberNamePrefix}newInstance(value) { if (it.isFailure) { - Log.w( - "${proxyApiCodecName(const InternalKotlinOptions(kotlinOut: ''))}", - "Failed to create new Dart proxy instance of ${api.name}: \$value. \${it.exceptionOrNull()}") + logNewInstanceFailure("${api.name}", value, it.exceptionOrNull()) } } }'''); diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt index b68ea2f3bcc..409fd62c7cc 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/ProxyApiTests.gen.kt @@ -490,36 +490,34 @@ private class ProxyApiTestsPigeonProxyApiBaseCodec( return } + fun logNewInstanceFailure(apiName: String, value: Any, exception: Throwable?) { + Log.w( + "PigeonProxyApiBaseCodec", + "Failed to create new Dart proxy instance of $apiName: $value. $exception") + } + if (value is ProxyApiTestClass) { registrar.getPigeonApiProxyApiTestClass().pigeon_newInstance(value) { if (it.isFailure) { - Log.w( - "PigeonProxyApiBaseCodec", - "Failed to create new Dart proxy instance of ProxyApiTestClass: $value. ${it.exceptionOrNull()}") + logNewInstanceFailure("ProxyApiTestClass", value, it.exceptionOrNull()) } } } else if (value is com.example.test_plugin.ProxyApiSuperClass) { registrar.getPigeonApiProxyApiSuperClass().pigeon_newInstance(value) { if (it.isFailure) { - Log.w( - "PigeonProxyApiBaseCodec", - "Failed to create new Dart proxy instance of ProxyApiSuperClass: $value. ${it.exceptionOrNull()}") + logNewInstanceFailure("ProxyApiSuperClass", value, it.exceptionOrNull()) } } } else if (value is ProxyApiInterface) { registrar.getPigeonApiProxyApiInterface().pigeon_newInstance(value) { if (it.isFailure) { - Log.w( - "PigeonProxyApiBaseCodec", - "Failed to create new Dart proxy instance of ProxyApiInterface: $value. ${it.exceptionOrNull()}") + logNewInstanceFailure("ProxyApiInterface", value, it.exceptionOrNull()) } } } else if (android.os.Build.VERSION.SDK_INT >= 25 && value is ClassWithApiRequirement) { registrar.getPigeonApiClassWithApiRequirement().pigeon_newInstance(value) { if (it.isFailure) { - Log.w( - "PigeonProxyApiBaseCodec", - "Failed to create new Dart proxy instance of ClassWithApiRequirement: $value. ${it.exceptionOrNull()}") + logNewInstanceFailure("ClassWithApiRequirement", value, it.exceptionOrNull()) } } }