From cf3fdf1f79564f7a256534ac0d0007e1beb75520 Mon Sep 17 00:00:00 2001 From: "free.li" Date: Sat, 1 Jul 2023 21:10:46 +0800 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFEnable=20Intercept=20function=20call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jint.Tests/Runtime/InteropTests.cs | 58 +++++++++++++++++++ .../Native/Function/ScriptFunctionInstance.cs | 28 ++++++++- Jint/Options.cs | 9 ++- .../Function/FunctionExecutionContext.cs | 17 ++++++ .../Interpreter/JintFunctionDefinition.cs | 2 +- 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 Jint/Runtime/Interop/Function/FunctionExecutionContext.cs diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 771eab8833..e8510a45c7 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using Esprima.Ast; using Jint.Native; using Jint.Native.Object; using Jint.Native.Symbol; @@ -3251,5 +3252,62 @@ public void CanPassDateTimeMinAndMaxViaInterop() engine.Execute("capture(maxDate);"); Assert.Equal(DateTime.MaxValue, dt); } + + [Fact] + public void CanInterceptFunctionCallViaInterop() + { + var records = new Dictionary(); + var engine = new Engine(cfg => + { + cfg.Interop.FunctionExecuting = ctx => + { + var location = ((Node) ctx.Function).Location; + var record = new FunctionCallRecord + { + Name = ctx.Function.Id?.Name, + StartLine = location.Start.Line, + EndLine = location.End.Line, + Args = ctx.Arguments.Select(x => x.ToObject()).ToArray() + }; + records.Add(ctx.UniqueId, record); + }; + cfg.Interop.FunctionExecuted = ctx => + { + if (records.TryGetValue(ctx.UniqueId, out var record) && ctx.Result is { Type: CompletionType.Return }) + { + record.Result = ctx.Result.Value.Value.ToObject(); + } + }; + }); + const string Js = @" +function main() { + return add(1, 2); +} +function add(a, b) { + return a + b; +}"; + var script = Engine.PrepareScript(Js.TrimStart()); + engine.Execute(script); + var result = engine.Invoke("main").ToObject(); + Assert.Equal(3, Convert.ToInt32(result)); + var traces = records.Values.ToList(); + Assert.Equal(2, traces.Count); + Assert.Equal("main", traces[0].Name); + Assert.Equal(3, Convert.ToInt32(traces[0].Result)); + Assert.Equal(2, traces[1].Args.Length); + Assert.Equal(1, Convert.ToInt32(traces[1].Args[0])); + Assert.Equal(2, Convert.ToInt32(traces[1].Args[1])); + Assert.Equal(1, traces[0].StartLine); + Assert.Equal(3, traces[0].EndLine); + } + + private class FunctionCallRecord + { + public string Name { get; set; } + public int StartLine { get; set; } + public int EndLine { get; set; } + public object[] Args { get; set; } + public object Result { get; set; } + } } } diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index c284bdf750..bd21100448 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -4,6 +4,7 @@ using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; using Jint.Runtime.Environments; +using Jint.Runtime.Interop.Function; using Jint.Runtime.Interpreter; namespace Jint.Native.Function @@ -75,8 +76,33 @@ protected internal override JsValue Call(JsValue thisArgument, JsValue[] argumen // actual call var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); - + FunctionExecutionContext? executionContext = null; + if (context.Engine.Options.Interop.FunctionExecuting is { } executing) + { + executionContext = new FunctionExecutionContext + { + UniqueId = Guid.NewGuid(), + Engine = context.Engine, + FunctionInstance = this, + Function = _functionDefinition.Function, + Arguments = arguments + }; + executing(executionContext); + } var result = _functionDefinition.EvaluateBody(context, this, arguments); + if (context.Engine.Options.Interop.FunctionExecuted is { } executed) + { + executionContext ??= new FunctionExecutionContext + { + UniqueId = Guid.NewGuid(), + Engine = context.Engine, + Function = _functionDefinition.Function, + FunctionInstance = this, + Arguments = arguments + }; + executionContext.Result = result; + executed(executionContext); + } if (result.Type == CompletionType.Throw) { diff --git a/Jint/Options.cs b/Jint/Options.cs index 157cf4247e..d32bae0c09 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -5,11 +5,12 @@ using Jint.Native; using Jint.Native.Object; using Jint.Runtime; -using Jint.Runtime.Interop; +using Jint.Runtime.CallStack; using Jint.Runtime.Debugger; using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; +using Jint.Runtime.Interop.Function; using Jint.Runtime.Modules; -using Jint.Runtime.CallStack; namespace Jint { @@ -334,6 +335,10 @@ public class InteropOptions /// public Func? SerializeToJson { get; set; } + public Action? FunctionExecuting { get; set; } + + public Action? FunctionExecuted { get; set; } + /// /// What kind of date time should be produced when JavaScript date is converted to DateTime. If Local, uses . /// Defaults to . diff --git a/Jint/Runtime/Interop/Function/FunctionExecutionContext.cs b/Jint/Runtime/Interop/Function/FunctionExecutionContext.cs new file mode 100644 index 0000000000..2a227eb170 --- /dev/null +++ b/Jint/Runtime/Interop/Function/FunctionExecutionContext.cs @@ -0,0 +1,17 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Function; + +#nullable disable + +namespace Jint.Runtime.Interop.Function; + +public class FunctionExecutionContext +{ + public Guid UniqueId { get; set; } + public Engine Engine { get; set; } + public IFunction Function { get; set; } + public FunctionInstance FunctionInstance { get; set; } + public JsValue[] Arguments { get; set; } + public Completion? Result { get; set; } +} diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index f68388eae6..f640263c60 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -415,7 +415,7 @@ private static void ProcessParameters( out bool hasArguments) { hasArguments = false; - state.IsSimpleParameterList = true; + state.IsSimpleParameterList = true; var countParameters = true; ref readonly var functionDeclarationParams = ref function.Params;