diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index b0e95741..ab5d562e 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -482,8 +482,11 @@ internal static Type[] RentPooledOrNewClosureTypeToParamTypes(ParamExprs paramEx var count = paramExprs.GetParamCount(); if (count == 0) return _closureAsASingleParamType; - - var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : new Type[count + 1]; + // The hidden closure argument also counts toward the CLI signature limit. + // The pooled small-signature path is already below that limit. + var pooledOrNew = count < 8 ? Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[count], null) ?? new Type[count + 1] : + count < ushort.MaxValue ? new Type[count + 1] : + throw new InvalidProgramException("The lambda and its closure exceed the maximum method parameter count."); pooledOrNew[0] = typeof(ArrayClosure); for (var i = 0; i < count; i++) { diff --git a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs index 7bdfc33b..15adaa36 100644 --- a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs @@ -34,6 +34,7 @@ static void RunLightExpressionTests(object state) var justLightTestsStopwatch = Stopwatch.StartNew(); var t = (LightExpression.TestRun)state; + t.Run(new LightExpression.UnitTests.ExcessiveArityRegressionTests()); t.Run(new LightExpression.UnitTests.NestedStructFieldAddressRegressionTests()); t.Run(new LightExpression.IssueTests.Issue183_NullableDecimal()); t.Run(new LightExpression.IssueTests.Issue398_Optimize_Switch_with_OpCodes_Switch()); @@ -60,6 +61,7 @@ static void RunLightExpressionTests(object state) var fecTestsStopwatch = Stopwatch.StartNew(); var fecTests = new TestRun(); + fecTests.Run(new ExcessiveArityRegressionTests()); fecTests.Run(new NestedStructFieldAddressRegressionTests()); fecTests.Run(new Issue183_NullableDecimal()); fecTests.Run(new Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET()); diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index ca07a0ca..85e80bd7 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -63,6 +63,7 @@ public static void Main(string[] args) var ltTask = Task.Run(() => { var ltStopwatch = Stopwatch.StartNew(); + lt.Run(new LightExpression.UnitTests.ExcessiveArityRegressionTests()); #if NET8_0_OR_GREATER lt.Run(new LightExpression.UnitTests.NestedStructFieldAddressRegressionTests()); lt.Run(new LightExpression.IssueTests.Issue490_Regression_in_compiling_lambdas_with_ref_struct_parameters()); @@ -86,6 +87,7 @@ public static void Main(string[] args) }); var stStopwatch = Stopwatch.StartNew(); + st.Run(new ExcessiveArityRegressionTests()); st.Run(new NestedStructFieldAddressRegressionTests()); st.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); st.Run(new Issue561_NonBooleanEqualityOperator()); diff --git a/test/FastExpressionCompiler.UnitTests/ExcessiveArityRegressionTests.cs b/test/FastExpressionCompiler.UnitTests/ExcessiveArityRegressionTests.cs new file mode 100644 index 00000000..79345003 --- /dev/null +++ b/test/FastExpressionCompiler.UnitTests/ExcessiveArityRegressionTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using S = System.Linq.Expressions.Expression; +#if LIGHT_EXPRESSION +namespace FastExpressionCompiler.LightExpression.UnitTests; +#else +namespace FastExpressionCompiler.UnitTests; +#endif + +public struct ExcessiveArityRegressionTests : ITestX +{ + public void Run(TestRun t) + { + Unrepresentable_method_signatures_are_rejected_during_compilation(t); + Large_supported_signatures_keep_argument_indices(t); + } + + public void Unrepresentable_method_signatures_are_rejected_during_compilation(TestContext t) + { + var parameters = Enumerable.Range(0, ushort.MaxValue).Select(_ => S.Parameter(typeof(int))).ToArray(); + var system = S.Lambda(parameters[parameters.Length - 1], parameters); + var expression = system.FromSysExpression(); + t.Throws(() => expression.CompileFast(true)); + t.Throws(() => expression.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression)); + t.Throws(() => system.Compile()); + } + + public void Large_supported_signatures_keep_argument_indices(TestContext t) + { + foreach (var count in new[] { 255, 256, 300 }) + { + var parameters = Enumerable.Range(0, count).Select(_ => S.Parameter(typeof(int))).ToArray(); + var system = S.Lambda(S.Add(parameters[0], parameters[count - 1]), parameters); + var fast = system.FromSysExpression().CompileFast(true); + if (!t.IsNotNull(fast)) return; + var arguments = Enumerable.Range(0, count).Select(i => (object)i).ToArray(); + t.AreEqual(count - 1, fast.DynamicInvoke(arguments)); + t.AreEqual(system.Compile().DynamicInvoke(arguments), fast.DynamicInvoke(arguments)); + } + } +}