From 2db15349798d442d97aac3ff82d5c91417e7e874 Mon Sep 17 00:00:00 2001 From: imb591 Date: Fri, 28 Jun 2024 19:18:29 +0300 Subject: [PATCH] Fix access to hoisted variables from reductions LambdaCompiler compiles several types of expressions by reducing them. Reductions may contain BlockExpressions not processed by VariableBinder, and in that case a scope is created for them on the fly during compilation. In particular, SwitchExpression and TypeBinaryExpression compilation can create a BlockExpression with a scope. If such a BlockExpression is an immediate child of a LambdaExpression, it lost access to lambda parameters that were hoisted due to the propagation of the NeedsClosure flag for its scope from the lambda. Always set the flag for such scopes to true, so that its scope always gets access to hoisted variables from the parent scope. Fix #56262 --- .../Compiler/LambdaCompiler.Statements.cs | 2 +- .../tests/Switch/SwitchTests.cs | 38 +++++++++++++++++++ .../tests/TypeBinary/TypeEqual.cs | 18 +++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Statements.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Statements.cs index 730bbb377990d..49353a2a4cb9f 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Statements.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Statements.cs @@ -100,7 +100,7 @@ private void EnterScope(object node) // User-created blocks will never hit this case; only our // internally reduced nodes will. // - scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure }; + scope = new CompilerScope(node, false) { NeedsClosure = true }; } _scope = scope.Enter(this, _scope); diff --git a/src/libraries/System.Linq.Expressions/tests/Switch/SwitchTests.cs b/src/libraries/System.Linq.Expressions/tests/Switch/SwitchTests.cs index dac5f1471b6a8..6023b7fdec941 100644 --- a/src/libraries/System.Linq.Expressions/tests/Switch/SwitchTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/Switch/SwitchTests.cs @@ -951,6 +951,44 @@ public void SwitchOnStringEqualsMethod(bool useInterpreter) } } + [Theory, ClassData(typeof(CompilationTypes))] + public void SwitchOnStringWithAccessToLambdaHoistedVariables(bool useInterpreter) + { + ParameterExpression arg0 = Expression.Parameter(typeof(string), "value"); + + // f1 = (Func f) => f(); + ParameterExpression arg1 = Expression.Parameter(typeof(Func), "f"); + Expression, int>> expr1 = + Expression.Lambda, int>>( + body: Expression.Invoke(arg1), + parameters: new[] { arg1 }); + + // f2 = () => value.Length; + Expression> expr2 = + Expression.Lambda>( + Expression.Property(arg0, typeof(string).GetProperty("Length"))); + + // f0 = (string value) => value switch { "1" => 1, ..., "7" => 7, _ => f1(f2) }; + Expression> expr0 = + Expression.Lambda>( + body: Expression.Switch( + switchValue: arg0, + defaultBody: Expression.Invoke(expr1, expr2), + cases: Enumerable.Range(1, 7).Select(index => + Expression.SwitchCase(Expression.Constant(index), Expression.Constant(index.ToString()))).ToArray()), + parameters: new[] { arg0 }); + + Func f0 = expr0.Compile(useInterpreter); + Assert.Equal(1, f0("1")); + Assert.Equal(2, f0("2")); + Assert.Equal(3, f0("3")); + Assert.Equal(4, f0("4")); + Assert.Equal(5, f0("5")); + Assert.Equal(6, f0("6")); + Assert.Equal(7, f0("7")); + Assert.Equal(2, f0("10")); + } + [Fact] public void ToStringTest() { diff --git a/src/libraries/System.Linq.Expressions/tests/TypeBinary/TypeEqual.cs b/src/libraries/System.Linq.Expressions/tests/TypeBinary/TypeEqual.cs index 2325e25c42973..9d3662cc9968f 100644 --- a/src/libraries/System.Linq.Expressions/tests/TypeBinary/TypeEqual.cs +++ b/src/libraries/System.Linq.Expressions/tests/TypeBinary/TypeEqual.cs @@ -196,6 +196,24 @@ public void TypeEqualConstant(Type type, bool useInterpreter) Assert.False(isNullOfType()); } + [Theory, ClassData(typeof(CompilationTypes))] + public void TypeEqualConstantWithAccessToLambdaHoistedVariables(bool useInterpreter) + { + ParameterExpression arg0 = Expression.Parameter(typeof(object), "value"); + + // f1 = () => value; + Expression> expr1 = Expression.Lambda>(arg0); + + // f0 = (object value) => (() => value) is int; + Expression> expr0 = Expression.Lambda>( + Expression.TypeEqual( + expr1, + typeof(int)), + parameters: new[] { arg0 }); + Func f0 = expr0.Compile(useInterpreter); + Assert.False(f0(null)); + } + [Fact] public void ToStringTest() {