From 93d2140cc06c6e386d3c6215acef43da9a2e4a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Korczy=C5=84ski?= Date: Thu, 9 May 2024 13:39:26 +0100 Subject: [PATCH] Wrap type conversions in try-catch to prevent crashes due to unhandled exceptions (#15640) --- .../Data/Core/TargetTypeConverter.cs | 36 +++++++++++++++--- .../TextBoxTests_DataValidation.cs | 37 +++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs b/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs index 2efc5b42bd..57018bff55 100644 --- a/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs +++ b/src/Avalonia.Base/Data/Core/TargetTypeConverter.cs @@ -76,16 +76,32 @@ internal abstract class TargetTypeConverter if (toTypeConverter.CanConvertFrom(from)) { - result = toTypeConverter.ConvertFrom(null, culture, value); - return true; + try + { + result = toTypeConverter.ConvertFrom(null, culture, value); + return true; + } + catch + { + result = null; + return false; + } } var fromTypeConverter = TypeDescriptor.GetConverter(from); if (fromTypeConverter.CanConvertTo(t)) { - result = fromTypeConverter.ConvertTo(null, culture, value, t); - return true; + try + { + result = fromTypeConverter.ConvertTo(null, culture, value, t); + return true; + } + catch + { + result = null; + return false; + } } // TODO: This requires reflection: we probably need to make compiled bindings emit @@ -95,8 +111,16 @@ internal abstract class TargetTypeConverter t, OperatorType.Implicit | OperatorType.Explicit) is { } cast) { - result = cast.Invoke(null, new[] { value }); - return true; + try + { + result = cast.Invoke(null, new[] { value }); + return true; + } + catch + { + result = null; + return false; + } } #pragma warning restore IL2067 #pragma warning restore IL2026 diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 295fc192d7..0eaa3092e9 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs @@ -6,8 +6,11 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Headless; using Avalonia.Markup.Data; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using Avalonia.Platform; using Avalonia.UnitTests; using Moq; @@ -110,6 +113,40 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void CompiledBindings_TypeConverter_Exceptions_Should_Set_DataValidationErrors_HasErrors() + { + var path = new CompiledBindingPathBuilder() + .Property( + new ClrPropertyInfo( + nameof(ExceptionTest.LessThan10), + target => ((ExceptionTest)target).LessThan10, + (target, value) => ((ExceptionTest)target).LessThan10 = (int)value, + typeof(int)), + PropertyInfoAccessorFactory.CreateInpcPropertyAccessor) + .Build(); + + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + DataContext = new ExceptionTest(), + [!TextBox.TextProperty] = new CompiledBindingExtension + { + Source = new ExceptionTest(), + Path = path, + Mode = BindingMode.TwoWay + }, + Template = CreateTemplate(), + }; + + target.ApplyTemplate(); + + target.Text = "a"; + Assert.True(DataValidationErrors.GetHasErrors(target)); + } + } + private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of(), textShaperImpl: new HeadlessTextShaperStub(),