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

Migrate PlatformMessageHandlerIos to ARC #52226

Merged
merged 3 commits into from
May 1, 2024

Conversation

jmagman
Copy link
Member

@jmagman jmagman commented Apr 18, 2024

Smart pointers support ARC as of #47612, and the unit tests were migrated in #48162.

Migrate PlatformMessageHandlerIos from MRC to ARC. Clean up the #includes.

Part of flutter/flutter#137801.

@jmagman jmagman self-assigned this Apr 18, 2024
@jmagman jmagman marked this pull request as ready for review April 18, 2024 20:30
@@ -127,8 +124,7 @@ - (void)dispatch:(dispatch_block_t)block {
message_handlers_.erase(channel);
if (handler) {
message_handlers_[channel] = {
.task_queue = fml::scoped_nsprotocol(
[static_cast<NSObject<FlutterTaskQueueDispatch>*>(task_queue) retain]),
.task_queue = (NSObject<FlutterTaskQueueDispatch>*)task_queue,
Copy link
Member

Choose a reason for hiding this comment

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

This is possibly unsafe. You are storing an objc pointer inside of a c++ data structure and relying on ARC to keep it alive. I'm like 60% sure this isn't okay, maybe it's fine if the structs and c++ data structures are only ever used from objc++ with ARC?

If you haven't already, you could write a tiny sample program to demonstrate that it works. You should probably add a comment if it is safe, you could even throw in a the link to the gist that proves it's safe. You may still have to use a smart pointer that uses bridge directives to manually do the reference counting.

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g Apr 19, 2024

Choose a reason for hiding this comment

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

maybe it's fine if the structs and c++ data structures are only ever used from objc++ with ARC?

It is, but since the struct is part of the public part of the header and doesn't assert ARC I agree that this isn't currently safe.

Let's leave this as a scoped_nsprotocol for now, and unwind it when the ARC migration is complete and it's easier to reason about the safety.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for keeping this on the rails, @gaaclarke. I am out of my depth with the parts of this migration that touch C++.

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -37,7 +34,7 @@ - (void)dispatch:(dispatch_block_t)block {
namespace flutter {

NSObject<FlutterTaskQueue>* PlatformMessageHandlerIos::MakeBackgroundTaskQueue() {
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, do we need a __attribute__((ns_returns_autoreleased)) for this method in case it is ever used from MRC? Are we sure that it isn't? Maybe we could add an arc assert in the header.

Without it I think ARC reserves the right to return a retained value, expecting the call site to release it. A MRC client would expect it to be autoreleased.

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought ARC followed the standard Create/Copy naming rules.

Copy link
Contributor

Choose a reason for hiding this comment

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

https://clang.llvm.org/docs/AutomaticReferenceCounting.html#conversion-to-retainable-object-pointer-type-of-expressions-with-known-semantics discusses what happens with audited functions, which are new since I had last looked at this in detail. We could add the audit annotation to all of our helpers that follow standard semantics (which I hope is all of them, but we should check) as we go to make it clearer that we know what the semantics are.

So far I can't find discussion of what happens when functions aren't audited, but I'm almost positive that when ARC first rolled out the rule was that it would follow the naming rules (i.e., that everything acted audited). It seems unlikely they would have retroactively changed that since it would have been massively breaking, but if we mark as audited it'll be moot.

Copy link
Member

Choose a reason for hiding this comment

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

Here's the hypothetical I was thinking of. This is how the code will look for those that are using this function from MRC.

// MRC
void Foo::foo() {
  bar_ = [platform_message_handler_.MakeBackgroundTaskQueue() retain];
}

I checked the output for the following:

@interface Foo : NSObject
@end

@implementation Foo
@end

Foo* MakeFoo() {
  return [[Foo alloc] init];
}

The output is

__Z7MakeFoov:                           ; @_Z7MakeFoov
	.cfi_startproc
; %bb.0:
	stp	x29, x30, [sp, #-16]!           ; 16-byte Folded Spill
	.cfi_def_cfa_offset 16
	mov	x29, sp
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	adrp	x8, _OBJC_CLASSLIST_REFERENCES_$_@PAGE
	ldr	x0, [x8, _OBJC_CLASSLIST_REFERENCES_$_@PAGEOFF]
	bl	_objc_alloc_init
	ldp	x29, x30, [sp], #16             ; 16-byte Folded Reload
	b	_objc_autoreleaseReturnValue
	.cfi_endproc
                                        ; -- End function
	.globl	__Z3Barv                        ; -- Begin function _Z3Barv
	.p2align	2

So it looks like autorelease is the default behavior, ns_returns_autoreleased seems superfluous in this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

So it looks like autorelease is the default behavior

That matches my recollection that ARC will honor CF-style naming conventions (since your method doesn't start with Create or Copy). But it's weird that I can't find anything that explicitly says that, and sprinkling cf_audited_transfer on our C functions as we go should be very easy.

.task_queue = fml::scoped_nsprotocol(
[static_cast<NSObject<FlutterTaskQueueDispatch>*>(task_queue) retain]),
.task_queue =
fml::scoped_nsprotocol(static_cast<NSObject<FlutterTaskQueueDispatch>*>(task_queue)),
Copy link
Member

Choose a reason for hiding this comment

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

This is similar to the comment on the other PR. ARC doesn't insert an unbalanced retain at a call site and the constructor for the scoped_nsprotocol uses __unsafe_unretained, assuming that it is coming in retained. So I think you need to add a retain here. Looks like ScopedTypeRef has a second parameter that you can use to specify that you want it to retain. See fml::scoped_policy::OwnershipPolicy

Copy link
Contributor

Choose a reason for hiding this comment

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

This is similar to the comment on the other PR. ARC doesn't insert an unbalanced retain at a call site and the constructor for the scoped_nsprotocol uses __unsafe_unretained, assuming that it is coming in retained.

As noted in my other responses: the version of scoped_* that we re-imported from Chromium was specifically, carefully constructed to have correct, equivalent ARC and non-ARC behaviors. In ARC there is no assuming anything about the retain state of the pointer being passed in, everything is annotated so that ARC can reason correctly about retain counts and adjust them accordingly.

Copy link
Member

Choose a reason for hiding this comment

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

This one is slightly different since the scoped_nsprotocol isn't consuming a ns_returns_not_retained return value.

scoped_nsprotocol is expected to consume a +1 object as seen here:

  explicit ScopedTypeRef(
      __unsafe_unretained T object = Traits::InvalidValue(),
      fml::scoped_policy::OwnershipPolicy policy = fml::scoped_policy::kAssume)
      : object_(object) {
    if (object_ && policy == fml::scoped_policy::kRetain) {
      object_ = Traits::Retain(object_);
    }
  }

In this new code, where would the extra retain come from that will match the one in the destructor for the scoped pointer? I don't think there is one.

Copy link
Member

Choose a reason for hiding this comment

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

Oh wait, i see it now:

#if !defined(__has_feature) || !__has_feature(objc_arc)
  explicit scoped_nsprotocol(NST object = Traits::InvalidValue(),
                             scoped_policy::OwnershipPolicy policy =
                                 scoped_policy::OwnershipPolicy::kAssume)
      : ScopedTypeRef<NST, Traits>(object, policy) {}
#else
  explicit scoped_nsprotocol(NST object = Traits::InvalidValue())
      : ScopedTypeRef<NST, Traits>(object,
                                   scoped_policy::OwnershipPolicy::kRetain) {}
#endif

@jmagman jmagman force-pushed the PlatformMessageHandlerIos branch from 9002f44 to c80f585 Compare April 25, 2024 23:36
@jmagman jmagman added the autosubmit Merge PR when tree becomes green via auto submit App label May 1, 2024
@auto-submit auto-submit bot merged commit 2d73fa2 into flutter:main May 1, 2024
29 checks passed
@jmagman jmagman deleted the PlatformMessageHandlerIos branch May 1, 2024 21:46
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 1, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request May 1, 2024
…147678)

flutter/engine@c536a14...2d73fa2

2024-05-01 [email protected] Migrate PlatformMessageHandlerIos to ARC (flutter/engine#52226)
2024-05-01 [email protected] macOS fluttertextinputplugin drops selector called if no client (flutter/engine#52495)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@jmagman jmagman mentioned this pull request Oct 5, 2024
9 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App platform-ios
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants