fix(apple): prevent double completionHandler call in PhonePermissionStrategy - #1566
ryanaidilp wants to merge 2 commits into
Conversation
…trategy checkServiceStatus called completionHandler with ServiceStatusNotApplicable when canOpenURL: returned NO, but fell through to call completionHandler again with the enabled/disabled status since there was no early return. Flutter result callbacks must only be invoked once; the second call can crash on devices that can't open tel:// URLs (e.g. iPad).
|
@mvanbeusekom can you please review this PR? Thanks |
ibrahim-iqbal
left a comment
There was a problem hiding this comment.
Confirmed the bug on master — PhonePermissionStrategy.m:22-24 invokes completionHandler(ServiceStatusNotApplicable) inside the if (![app canOpenURL:telURL]) branch and then falls straight through to the trailing completionHandler([self canDevicePlaceAPhoneCall] ? ServiceStatusEnabled : ServiceStatusDisabled) on line 25. Two invocations of the same handler for the branch where tel:// is not openable, which is the iPad case in #1565. Adding return; after the first call is the minimal fix and stops the extra canDevicePlaceAPhoneCall CoreTelephony work at the same time.
Cross-checked the neighbouring strategies to back the "no other strategy has the same fall-through" claim from the PR body:
AppTrackingTransparencyPermissionStrategy.m— everyifbranch that calls the handlerreturns.AssistantPermissionStrategy.m— single-statement body.AudioVideoPermissionStrategy.m,BackgroundRefreshStrategy.m,BluetoothPermissionStrategy.m— same shape, each handler-invoking branch is followed by areturnor is the terminal statement.
So this is genuinely the only offender.
One optional readability nudge, take or leave: if (![app canOpenURL:telURL]) { completionHandler(ServiceStatusNotApplicable); return; } else { completionHandler(...); } or an else { ... } around the trailing call would make the "these two are mutually exclusive" contract structural rather than implied by control flow. return; alone works and is the smallest possible diff, which is probably the right trade for a hot-fix on a production crash.
Good point on readability. I went this route to keep the code changes minimal, but I agree your approach is better. I'll wrap it in an else block and push the update. Thanks |
…onStrategy Makes the mutual exclusivity of the two completionHandler branches structural rather than implied by control flow, per review feedback from @ibrahim-iqbal.
Summary
checkServiceStatusinPhonePermissionStrategy.mcalledcompletionHandler(ServiceStatusNotApplicable)inside theif (![app canOpenURL:telURL])branch without returning, so execution fell through and calledcompletionHandlera second time with the enabled/disabled status.canOpenURL:returnsNOfortel://(e.g. iPad).return;socompletionHandleris only ever invoked once.Test plan
*PermissionStrategy.mfile underpermission_handler_appleto confirm no other strategy has the same missing-returnfallthrough pattern (all others either early-returnafter each handler call, or have a single-statement stub body).canOpenURL:returnsNOfortel://(e.g. iPad) to confirmcheckServiceStatusforPermission.phoneno longer crashes.