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 3, 2023
1 parent db73cad commit c612b01
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 3 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; }
}
}
}
13 changes: 11 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 All @@ -19,6 +20,10 @@ namespace Jint

public delegate bool ExceptionHandlerDelegate(Exception exception);

public delegate void FunctionExecutingDelegate(FunctionExecutionContext context);

public delegate void FunctionExecutedDelegate(FunctionExecutionContext context);

public class Options
{
private ITimeSystem? _timeSystem;
Expand Down Expand Up @@ -334,6 +339,10 @@ public class InteropOptions
/// </summary>
public Func<object, string>? SerializeToJson { get; set; }

public FunctionExecutingDelegate? FunctionExecuting { get; set; }

public FunctionExecutedDelegate? 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; }
}
31 changes: 30 additions & 1 deletion Jint/Runtime/Interpreter/JintFunctionDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Runtime.CompilerServices;
using Esprima;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Argument;
using Jint.Native.Function;
using Jint.Native.Promise;
using Jint.Runtime.Environments;
using Jint.Runtime.Interop.Function;
using Jint.Runtime.Interpreter.Expressions;

namespace Jint.Runtime.Interpreter;
Expand Down Expand Up @@ -38,6 +40,19 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
{
Completion result;
ArgumentsInstance? argumentsInstance = null;
FunctionExecutionContext? executionContext = null;
if (context.Engine.Options.Interop.FunctionExecuting is not null)
{
executionContext = new FunctionExecutionContext
{
Engine = context.Engine,
FunctionInstance = functionObject,
Function = Function,
Arguments = argumentsList,
UniqueId = Guid.NewGuid()
};
context.Engine.Options.Interop.FunctionExecuting(executionContext);
}
if (Function.Expression)
{
// https://tc39.es/ecma262/#sec-runtime-semantics-evaluateconcisebody
Expand Down Expand Up @@ -90,6 +105,20 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
}

argumentsInstance?.FunctionWasCalled();

if (context.Engine.Options.Interop.FunctionExecuted is not null)
{
executionContext ??= new FunctionExecutionContext()
{
Engine = context.Engine,
Function = Function,
FunctionInstance = functionObject,
Arguments = argumentsList,
UniqueId = Guid.NewGuid()
};
executionContext.Result = result;
context.Engine.Options.Interop.FunctionExecuted(executionContext);
}
return result;
}

Expand Down Expand Up @@ -415,7 +444,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 c612b01

Please sign in to comment.