Skip to content

Commit

Permalink
Enable Intercept function call
Browse files Browse the repository at this point in the history
  • Loading branch information
libaowei committed Jul 17, 2023
1 parent db73cad commit cf3fdf1
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 4 deletions.
58 changes: 58 additions & 0 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3251,5 +3252,62 @@ public void CanPassDateTimeMinAndMaxViaInterop()
engine.Execute("capture(maxDate);");
Assert.Equal(DateTime.MaxValue, dt);
}

[Fact]
public void CanInterceptFunctionCallViaInterop()
{
var records = new Dictionary<Guid, FunctionCallRecord>();
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; }
}
}
}
28 changes: 27 additions & 1 deletion Jint/Native/Function/ScriptFunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down
9 changes: 7 additions & 2 deletions Jint/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -334,6 +335,10 @@ public class InteropOptions
/// </summary>
public Func<object, string>? SerializeToJson { get; set; }

public Action<FunctionExecutionContext>? FunctionExecuting { get; set; }

public Action<FunctionExecutionContext>? FunctionExecuted { get; set; }

/// <summary>
/// What kind of date time should be produced when JavaScript date is converted to DateTime. If Local, uses <see cref="Options.TimeZone"/>.
/// Defaults to <see cref="System.DateTimeKind.Utc"/>.
Expand Down
17 changes: 17 additions & 0 deletions Jint/Runtime/Interop/Function/FunctionExecutionContext.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
2 changes: 1 addition & 1 deletion Jint/Runtime/Interpreter/JintFunctionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit cf3fdf1

Please sign in to comment.