From 54e8187a70daaf2851d442279ddc14aa030fb62f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 12 Oct 2017 23:37:37 -0500 Subject: [PATCH] Fix StackOverflow in Negation Binding. Fixes #1213 --- .../Data/ExpressionObserver.cs | 19 +++++++++++++++++- .../Avalonia.Markup/Data/ITransformNode.cs | 11 ++++++++++ .../Avalonia.Markup/Data/LogicalNotNode.cs | 13 +++++++++++- .../Data/ExpressionObserverTests_Negation.cs | 20 ++++++++++++++++++- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/Markup/Avalonia.Markup/Data/ITransformNode.cs diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs index 66d3beb907..dd9718a0f6 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs @@ -154,7 +154,24 @@ namespace Avalonia.Markup.Data /// public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) { - return (Leaf as ISettableNode)?.SetTargetValue(value, priority) ?? false; + if (Leaf is ISettableNode settable) + { + var node = _node; + while (node != null) + { + if (node is ITransformNode transform) + { + value = transform.Transform(value); + if (value is BindingNotification) + { + return false; + } + } + node = node.Next; + } + return settable.SetTargetValue(value, priority); + } + return false; } /// diff --git a/src/Markup/Avalonia.Markup/Data/ITransformNode.cs b/src/Markup/Avalonia.Markup/Data/ITransformNode.cs new file mode 100644 index 0000000000..f33ecd3722 --- /dev/null +++ b/src/Markup/Avalonia.Markup/Data/ITransformNode.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Markup.Data +{ + interface ITransformNode + { + object Transform(object value); + } +} diff --git a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs b/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs index 58a7915254..ae68867e82 100644 --- a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs +++ b/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs @@ -7,7 +7,7 @@ using Avalonia.Data; namespace Avalonia.Markup.Data { - internal class LogicalNotNode : ExpressionNode + internal class LogicalNotNode : ExpressionNode, ITransformNode { public override string Description => "!"; @@ -61,5 +61,16 @@ namespace Avalonia.Markup.Data return AvaloniaProperty.UnsetValue; } + + public object Transform(object value) + { + var originalType = value.GetType(); + var negated = Negate(value); + if (negated is BindingNotification) + { + return negated; + } + return Convert.ChangeType(negated, originalType); + } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs index d8dc2de847..6a23a24051 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs @@ -105,14 +105,32 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public void SetValue_Should_Return_False() + public void SetValue_Should_Return_False_For_Invalid_Value() { var data = new { Foo = "foo" }; var target = new ExpressionObserver(data, "!Foo"); + target.Subscribe(_ => { }); Assert.False(target.SetValue("bar")); GC.KeepAlive(data); } + + [Fact] + public void Can_SetValue_For_Valid_Value() + { + var data = new Test { Foo = true }; + var target = new ExpressionObserver(data, "!Foo"); + target.Subscribe(_ => { }); + + Assert.True(target.SetValue(true)); + + Assert.False(data.Foo); + } + + private class Test + { + public bool Foo { get; set; } + } } }