From 6c4bb8405be9f7cdcb15f224993bee5b3d2021b9 Mon Sep 17 00:00:00 2001 From: praveenkumarkarunanithi Date: Wed, 5 Aug 2026 18:12:53 +0530 Subject: [PATCH] NullRef Exception fix and unit test update (cherry picked from commit 204752ed60f3ead31248cfa33d7112b6a9b159ac) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 036f86e5-cc79-4a9c-a64d-443a82501748 --- src/Controls/src/Core/BindableObject.cs | 23 +++----- .../Core.UnitTests/BindableObjectUnitTests.cs | 55 +++++++++++++++++++ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/Controls/src/Core/BindableObject.cs b/src/Controls/src/Core/BindableObject.cs index 0e7d7d65c219..8b15eeee5c49 100644 --- a/src/Controls/src/Core/BindableObject.cs +++ b/src/Controls/src/Core/BindableObject.cs @@ -779,20 +779,15 @@ BindablePropertyContext CreateContext(BindableProperty property) [MethodImpl(MethodImplOptions.AggressiveInlining)] BindablePropertyContext GetOrCreateContext(BindableProperty property) { -#if NETSTANDARD - var context = GetContext(property); - if (context is null) - { - context = CreateContext(property); - _properties.Add(property.InternalId, context); - } -#else - ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_properties, property.InternalId, out var exists); - if (!exists) - { - context = CreateContext(property); - } -#endif + if (_properties.TryGetValue(property.InternalId, out var context)) + return context; + + // Do not use CollectionsMarshal.GetValueRefOrAddDefault: CreateContext invokes + // DefaultValueCreator, which is arbitrary user code and may mutate other + // BindableProperties, resizing _properties and invalidating the returned ref. + // See dotnet/maui#36744. + context = CreateContext(property); + _properties[property.InternalId] = context; return context; } diff --git a/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs b/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs index f1f9316cf5e7..fe15306eb41f 100644 --- a/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs +++ b/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs @@ -1717,5 +1717,60 @@ public void SpecificityOfHandlers() Assert.Equal("manual", bindable.GetValue(prop)); } + // Regression test for https://github.com/dotnet/maui/issues/36744 + [Fact] + public void DefaultValueCreatorThatMutatesOtherPropertiesDoesNotCorruptPropertyStore() + { + var mock = new MockBindable36744(); + + var triggerValue = mock.GetValue(MockBindable36744.TriggerProperty); + Assert.NotNull(triggerValue); + Assert.Same(triggerValue, mock.GetValue(MockBindable36744.TriggerProperty)); + + var exception = Record.Exception(() => mock.BindingContext = new object()); + Assert.Null(exception); + } + } + + internal class MockBindable36744 : BindableObject + { + public static readonly BindableProperty P0 = BindableProperty.Create("P0", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P1 = BindableProperty.Create("P1", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P2 = BindableProperty.Create("P2", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P3 = BindableProperty.Create("P3", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P4 = BindableProperty.Create("P4", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P5 = BindableProperty.Create("P5", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P6 = BindableProperty.Create("P6", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P7 = BindableProperty.Create("P7", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P8 = BindableProperty.Create("P8", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P9 = BindableProperty.Create("P9", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P10 = BindableProperty.Create("P10", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P11 = BindableProperty.Create("P11", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P12 = BindableProperty.Create("P12", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P13 = BindableProperty.Create("P13", typeof(int), typeof(MockBindable36744), 0); + public static readonly BindableProperty P14 = BindableProperty.Create("P14", typeof(int), typeof(MockBindable36744), 0); + + public static readonly BindableProperty TriggerProperty = BindableProperty.Create( + "Trigger", typeof(object), typeof(MockBindable36744), null, + defaultValueCreator: b => + { + var mb = (MockBindable36744)b; + mb.SetValue(P0, 1); + mb.SetValue(P1, 2); + mb.SetValue(P2, 3); + mb.SetValue(P3, 4); + mb.SetValue(P4, 5); + mb.SetValue(P5, 6); + mb.SetValue(P6, 7); + mb.SetValue(P7, 8); + mb.SetValue(P8, 9); + mb.SetValue(P9, 10); + mb.SetValue(P10, 11); + mb.SetValue(P11, 12); + mb.SetValue(P12, 13); + mb.SetValue(P13, 14); + mb.SetValue(P14, 15); + return new object(); + }); } }