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 4, 2023
1 parent db73cad commit 9d49eb3
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 3 deletions.
62 changes: 62 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,66 @@ 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 =>
{
#if !NETFRAMEWORK
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();
}
};
#endif
});
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));
#if !NETFRAMEWORK
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);
#endif
}

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; }
}
}
}
14 changes: 12 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 @@ -18,6 +19,11 @@ namespace Jint
public delegate ObjectInstance? WrapObjectDelegate(Engine engine, object target);

public delegate bool ExceptionHandlerDelegate(Exception exception);
#if !NETFRAMEWORK
public delegate void FunctionExecutingDelegate(FunctionExecutionContext context);

public delegate void FunctionExecutedDelegate(FunctionExecutionContext context);
#endif

public class Options
{
Expand Down Expand Up @@ -333,7 +339,11 @@ public class InteropOptions
/// <see cref="IObjectWrapper"/> passing through 'JSON.stringify'.
/// </summary>
public Func<object, string>? SerializeToJson { get; set; }
#if !NETFRAMEWORK
public FunctionExecutingDelegate? FunctionExecuting { get; set; }

public FunctionExecutedDelegate? FunctionExecuted { get; set; }
#endif
/// <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; }
}
33 changes: 32 additions & 1 deletion Jint/Runtime/Interpreter/JintFunctionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 +39,21 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
{
Completion result;
ArgumentsInstance? argumentsInstance = null;
#if !NETFRAMEWORK
FunctionExecutionContext? executionContext = null;
if (context.Engine.Options.Interop.FunctionExecuting != null)
{
executionContext = new FunctionExecutionContext
{
Engine = context.Engine,
FunctionInstance = functionObject,
Function = Function,
Arguments = argumentsList,
UniqueId = Guid.NewGuid()
};
context.Engine.Options.Interop.FunctionExecuting(executionContext);
}
#endif
if (Function.Expression)
{
// https://tc39.es/ecma262/#sec-runtime-semantics-evaluateconcisebody
Expand Down Expand Up @@ -90,6 +106,21 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
}

argumentsInstance?.FunctionWasCalled();
#if !NETFRAMEWORK
if (context.Engine.Options.Interop.FunctionExecuted != 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);
}
#endif
return result;
}

Expand Down Expand Up @@ -415,7 +446,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 9d49eb3

Please sign in to comment.