using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests; /// /// lmxopcua#779 — the stack-guard rewrite () must not change what a script /// means: every lambda / local-function shape still binds to the same overload and returns the same value, and /// bounded recursion keeps working. The process-level proof that unbounded recursion no longer kills the host is /// ; this class runs only scripts that terminate. /// public sealed class ScriptStackGuardInjectionTests { private static object? Run(string source) { using var evaluator = ScriptEvaluator.Compile(source); return evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken).Result; } [Theory] [InlineData("int F(int n) => n <= 0 ? 0 : F(n - 1) + 1; return F(500);", 500)] [InlineData("int F(int n) { if (n <= 0) return 0; return F(n - 1) + 1; } return F(500);", 500)] [InlineData("System.Func f = null!; f = n => n <= 0 ? 0 : f(n - 1) + 1; return f(500);", 500)] [InlineData("System.Func f = null!; f = delegate (int n) { return n <= 0 ? 0 : f(n - 1) + 1; }; return f(500);", 500)] public void Bounded_recursion_still_returns_its_value(string source, int expected) => Run(source).ShouldBe(expected); [Theory] // Expression lambdas through LINQ: Select / Where / Sum / Aggregate overloads resolve as before. [InlineData("return System.Linq.Enumerable.Sum(System.Linq.Enumerable.Select(new[] { 1, 2, 3 }, x => x * 2));", 12)] [InlineData("return System.Linq.Enumerable.Count(new[] { 1, 2, 3, 4 }, x => x % 2 == 0);", 2)] [InlineData("return System.Linq.Enumerable.Aggregate(new[] { 1, 2, 3 }, 10, (acc, x) => acc + x);", 16)] // An expression-bodied VOID lambda (List.ForEach takes an Action): rewritten to `{ Check(); e; }`. [InlineData("var acc = new System.Collections.Generic.List(); new System.Collections.Generic.List { 1, 2 }.ForEach(x => acc.Add(x * 5)); return acc[1];", 10)] // Expression-bodied void local function, and a local function returning a lambda (nested rewrite). [InlineData("var box = new int[1]; void Set(int v) => box[0] = v; Set(7); return box[0];", 7)] [InlineData("System.Func Make(int k) => x => x + k; return Make(3)(4);", 7)] // Static lambda and a lambda capturing ctx. [InlineData("System.Func f = static x => x - 1; return f(9);", 8)] [InlineData("System.Func known = p => ctx.GetTag(p).StatusCode == 0u; return known(\"missing\") ? 1 : 0;", 0)] public void Rewritten_lambdas_and_local_functions_keep_their_meaning(string source, int expected) => Run(source).ShouldBe(expected); /// A lambda converted to an expression tree is left as it is (a block body is illegal there and an /// expression tree runs no script frame); rewriting it would turn a compiling script into an emit failure. [Fact] public void An_expression_tree_lambda_is_not_rewritten() { Run("System.Linq.Expressions.Expression> e = x => x + 1; return (int)e.Body.NodeType;") .ShouldBe((int)System.Linq.Expressions.ExpressionType.Add); } }