From 8bdd655d1c14e4f5a85c09ddd9debd24a13b70c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrej=20=C4=8Ci=C5=BEm=C3=A1rik?= Date: Thu, 26 Aug 2021 14:20:31 +0200 Subject: [PATCH 1/2] Fixed issue when indexing expression evaluated to null --- .../ExpressionNullPropagationVisitor.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/DotVVM.Framework/Compilation/Binding/ExpressionNullPropagationVisitor.cs b/src/DotVVM.Framework/Compilation/Binding/ExpressionNullPropagationVisitor.cs index 310e715c6e..cf5006ec6b 100644 --- a/src/DotVVM.Framework/Compilation/Binding/ExpressionNullPropagationVisitor.cs +++ b/src/DotVVM.Framework/Compilation/Binding/ExpressionNullPropagationVisitor.cs @@ -130,12 +130,48 @@ protected override Expression VisitIndex(IndexExpression node) protected override Expression VisitMethodCall(MethodCallExpression node) { + if (node.Method.Attributes.HasFlag(MethodAttributes.SpecialName)) + { + var expr = TryVisitMethodCallWithSpecialName(node); + if (expr != null) + return expr; + } + return CheckForNull(Visit(node.Object), target => Expression.Call(target, node.Method, UnwrapNullableTypes(node.Arguments)), suppress: node.Object?.Type?.IsNullable() ?? true ); } + private Expression TryVisitMethodCallWithSpecialName(MethodCallExpression node) + { + // Check if we are translating an access to indexer property + if (node.Method.Name.StartsWith("get_", StringComparison.Ordinal) || node.Method.Name.StartsWith("set_", StringComparison.Ordinal)) + { + var targetType = node.Object?.Type; + if (targetType == null) + return null; + + var indexer = targetType.GetProperties().SingleOrDefault(p => p.GetIndexParameters().Length > 0); + if (indexer == null) + return null; + + if (!node.Method.Name.Equals($"get_{indexer.Name}", StringComparison.Ordinal) && !node.Method.Name.Equals($"set_{indexer.Name}", StringComparison.Ordinal)) + return null; + + return CheckForNull(Visit(node.Object), target => + { + return CheckForNull(Visit(node.Arguments.First()), index => + { + var convertedIndex = TypeConversion.ImplicitConversion(index, node.Method.GetParameters().First().ParameterType); + return Expression.Call(target, node.Method, new[] { convertedIndex }.Concat(node.Arguments.Skip(1))); + }); + }, suppress: node.Object?.Type?.IsNullable() ?? true); + } + + return null; + } + protected override Expression VisitNew(NewExpression node) { return Expression.New(node.Constructor, UnwrapNullableTypes(node.Arguments)); From 4ebe7d0f4998d86cadb18cf0c2a06d6b5c2c0057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrej=20=C4=8Ci=C5=BEm=C3=A1rik?= Date: Thu, 26 Aug 2021 14:20:56 +0200 Subject: [PATCH 2/2] Added more tests for null propagation --- .../Binding/BindingCompilationTests.cs | 3 +++ .../Binding/NullPropagationTests.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/DotVVM.Framework.Tests/Binding/BindingCompilationTests.cs b/src/DotVVM.Framework.Tests/Binding/BindingCompilationTests.cs index 6f82133f0f..16cb73df3d 100755 --- a/src/DotVVM.Framework.Tests/Binding/BindingCompilationTests.cs +++ b/src/DotVVM.Framework.Tests/Binding/BindingCompilationTests.cs @@ -961,6 +961,7 @@ class TestViewModel { public string StringProp { get; set; } public int IntProp { get; set; } + public int? NullableIntProp { get; set; } public double DoubleProp { get; set; } public TestViewModel2 TestViewModel2 { get; set; } public TestViewModel2 TestViewModel2B { get; set; } @@ -979,6 +980,8 @@ class TestViewModel public long[] LongArray => new long[] { 1, 2, long.MaxValue }; public List LongList => new List() { 1, 2, long.MaxValue }; public string[] StringArray => new string[] { "Hello ", "DotVVM" }; + public Dictionary StringVmDictionary = new() { { "a", new TestViewModel2() }, { "b", new TestViewModel2() } }; + public Dictionary NullableIntVmDictionary = new() { { 0, new TestViewModel2() }, { 1, new TestViewModel2() } }; public TestViewModel2[] VmArray => new TestViewModel2[] { new TestViewModel2() }; public int[] IntArray { get; set; } diff --git a/src/DotVVM.Framework.Tests/Binding/NullPropagationTests.cs b/src/DotVVM.Framework.Tests/Binding/NullPropagationTests.cs index c047f90f50..15f8fffd6c 100644 --- a/src/DotVVM.Framework.Tests/Binding/NullPropagationTests.cs +++ b/src/DotVVM.Framework.Tests/Binding/NullPropagationTests.cs @@ -285,6 +285,15 @@ public void Indexer() Assert.IsNull(EvalExpression(v => v.TestViewModel2.Collection[0].StringValue.Length + 5, new TestViewModel { IntArray = null })); } + [TestMethod] + public void IndexerArgument() + { + Assert.IsNull(EvalExpression(v => v.IntArray[v.NullableIntProp.Value], new TestViewModel())); + Assert.IsNull(EvalExpression(v => v.TestViewModel2.Collection[v.NullableIntProp.Value], new TestViewModel())); + Assert.IsNull(EvalExpression(v => v.StringVmDictionary[v.StringProp], new TestViewModel())); + Assert.IsNull(EvalExpression(v => v.NullableIntVmDictionary[v.NullableIntProp], new TestViewModel())); + } + [TestMethod] public void Coalesce() {