[iOS] NavigationRenderer.SetStatusBarStyle null check does not protect the following NavPage dereferences
- Dominant language
- C#
- Stars
- 23.3k
- Forks
- 2k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 290
Description
### Description
`NavigationRenderer.SetStatusBarStyle()` guards against a null `NavPage` and then dereferences it twice more. `NavPage` is a computed property backed by a weak reference, so the guard does not protect the two uses that follow — if the element is released or the handler disconnected in between, the method throws `NullReferenceException`.
https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
```csharp
NavigationPage NavPage => Element as NavigationPage; // re-evaluated on every access
public VisualElement Element { get => _viewHandlerWrapper.Element ?? _element?.GetTargetOrDefault(); } // weak reference
void SetStatusBarStyle()
{
if (NavPage is null) // access 1 - passes
{
return;
}
var barTextColor = NavPage.BarTextColor; // access 2 - can be null here
var statusBarColorMode = NavPage.OnThisPlatform().GetStatusBarTextColorMode(); // access 3
...
}
```
Because `SetStatusBarStyle()` is called from `ViewWillAppear`, the exception escapes through the ObjC registrar trampoline to `UIApplicationMain` and terminates the process, rather than surfacing as a catchable managed exception:
```
System.NullReferenceException: Arg_NullReferenceException
at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.SetStatusBarStyle()
at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.ViewWillAppear(Boolean animated)
at ...__Registrar_Callbacks__.callback_..._ViewWillAppear(IntPtr pobj, IntPtr sel, Byte p0, IntPtr* exception_gchandle)
at ObjCRuntime.Runtime.ThrowException(IntPtr gchandle)
at UIKit.UIApplication.UIApplicationMain(...)
```
This is the same signature as #29535 and #29564. Those were closed after the `if (NavPage is null) return;` guard was added, but the guard cannot fix this pattern: it validates a value that is then discarded and re-read. We still see the crash on builds that contain the guard.
The comment on #29535 that "NET9 does cleanup code a bit more aggressively than NET8" is consistent with this — more eager teardown widens the window between the check and the uses.
### Steps to Reproduce
We have not been able to reproduce this on demand, which is consistent with it being a race. It reproduces in the field for us when a third-party popup library repeatedly changes which `UIWindow` is key: the library demotes the app window (`WindowLevel = -1`), presents its own window with `MakeKeyAndVisible()`, and reverses that on dismissal. Each cycle drives an extra `ViewWillAppear` on the `NavigationRenderer` while views are being torn down and re-shown.
Crash breadcrumbs consistently show the popup/navigation cycle immediately before the crash:
```
PopupPageRenderer -> NavigationRenderer_ParentingViewController
PopupPageRenderer -> NavigationRenderer_ParentingViewController
PopupPageRenderer -> NavigationRenderer_ParentingViewController <- crash here
```
Any scenario that disconnects the handler or releases the page concurrently with `ViewWillAppear` should be able to hit it.
### Link to public reproduction project repository
_No response_
### Version with bug
10.0.71, 10.0.90, 10.0.100 (the method is byte-identical across these and current `main`)
### Is this a regression from previous behavior?
Yes, this used to work in .NET MAUI
### Last version that worked well
Unknown/Other
### Affected platforms
iOS
### Affected platform versions
iOS 26.x, seen across iPhone 12 through iPhone 16 Plus
### Did you find any workaround?
No workaround from application code — the race is entirely inside `SetStatusBarStyle()`. Avoiding whatever provokes the extra `ViewWillAppear` cycles reduces the frequency but does not address the cause.
### Relevant log output
```shell
System.NullReferenceException: Arg_NullReferenceException (System.NullReferenceException)
at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.SetStatusBarStyle()
at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.ViewWillAppear(Boolean animated)
at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.__Registrar_Callbacks__.callback_795_Microsoft_Maui_Controls_Handlers_Compatibility_NavigationRenderer_ViewWillAppear(IntPtr pobj, IntPtr sel, Byte p0, IntPtr* exception_gchandle)
at __exceptionPreprocess
at objc_exception_throw
at log_callback(char const*, char const*, char const*, int, void*) (runtime.m:1073)
at -[Microsoft_Maui_Controls_Handlers_Compatibility_NavigationRenderer viewWillAppear:] (registrar.mm:20168)
at -[UIViewController _setViewAppearState:isAnimating:]
at -[UIViewController __viewWillAppear:]
```
### Proposed fix
Read `NavPage` once into a local and use that, so the guard actually protects the subsequent uses:
```csharp
void SetStatusBarStyle()
{
var navPage = NavPage;
if (navPage is null)
{
return;
}
var barTextColor = navPage.BarTextColor;
var statusBarColorMode = navPage.OnThisPlatform().GetStatusBarTextColorMode();
// ... unchanged
}
```
This is behaviour-preserving when the element is alive and removes the crash when it is not.
It is worth noting that `NavPage` is dereferenced after a null check in several other places in this file (for example around the `Element == null` checks). The same pattern may be worth auditing, since every one of them has the same property-versus-local hazard.
Contributor guide
Research direction
Open src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs and trace ViewWillAppear into SetStatusBarStyle. Verify NavPage is read once before the null check and that the later accesses use the guarded value; also review the other NavPage checks noted in the file. Done when the race-prone dereferences are eliminated without changing the remaining behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100