diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 37451fd2da1bf..00637023f3e6e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -1217,9 +1217,11 @@ - (void)flutterViewAccessibilityDidCall { // remove this. - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger { // Discard the previous messenger and keep the new one. - _binaryMessenger.parent = nil; - [_binaryMessenger release]; - _binaryMessenger = [binaryMessenger retain]; + if (binaryMessenger != _binaryMessenger) { + _binaryMessenger.parent = nil; + [_binaryMessenger release]; + _binaryMessenger = [binaryMessenger retain]; + } } #pragma mark - FlutterBinaryMessenger diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 34743904f7cf6..64167b7496c45 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -22,6 +22,22 @@ @interface FlutterEngine () @end +/// FlutterBinaryMessengerRelay used for testing that setting FlutterEngine.binaryMessenger to +/// the current instance doesn't trigger a use-after-free bug. +/// +/// See: testSetBinaryMessengerToSameBinaryMessenger +@interface FakeBinaryMessengerRelay : FlutterBinaryMessengerRelay +@property(nonatomic, assign) BOOL failOnDealloc; +@end + +@implementation FakeBinaryMessengerRelay +- (void)dealloc { + if (_failOnDealloc) { + XCTFail("FakeBinaryMessageRelay should not be deallocated"); + } +} +@end + @interface FlutterEngineTest : XCTestCase @end @@ -148,6 +164,20 @@ - (void)testNotifyPluginOfDealloc { OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]); } +- (void)testSetBinaryMessengerToSameBinaryMessenger { + FakeBinaryMessengerRelay* fakeBinaryMessenger = [[FakeBinaryMessengerRelay alloc] init]; + + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine setBinaryMessenger:fakeBinaryMessenger]; + + // Verify that the setter doesn't free the old messenger before setting the new messenger. + fakeBinaryMessenger.failOnDealloc = YES; + [engine setBinaryMessenger:fakeBinaryMessenger]; + + // Don't fail when ARC releases the binary messenger. + fakeBinaryMessenger.failOnDealloc = NO; +} + - (void)testRunningInitialRouteSendsNavigationMessage { id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);