Skip to content

Commit 9d49eb3

Browse files
committed
Enable Intercept function call
1 parent db73cad commit 9d49eb3

File tree

4 files changed

+123
-3
lines changed

4 files changed

+123
-3
lines changed

Jint.Tests/Runtime/InteropTests.cs

+62
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Globalization;
33
using System.Reflection;
44
using System.Runtime.CompilerServices;
5+
using Esprima.Ast;
56
using Jint.Native;
67
using Jint.Native.Object;
78
using Jint.Native.Symbol;
@@ -3251,5 +3252,66 @@ public void CanPassDateTimeMinAndMaxViaInterop()
32513252
engine.Execute("capture(maxDate);");
32523253
Assert.Equal(DateTime.MaxValue, dt);
32533254
}
3255+
3256+
[Fact]
3257+
public void CanInterceptFunctionCallViaInterop()
3258+
{
3259+
var records = new Dictionary<Guid, FunctionCallRecord>();
3260+
var engine = new Engine(cfg =>
3261+
{
3262+
#if !NETFRAMEWORK
3263+
cfg.Interop.FunctionExecuting = ctx =>
3264+
{
3265+
var location = ((Node) ctx.Function).Location;
3266+
var record = new FunctionCallRecord
3267+
{
3268+
Name = ctx.Function.Id?.Name,
3269+
StartLine = location.Start.Line,
3270+
EndLine = location.End.Line,
3271+
Args = ctx.Arguments.Select(x => x.ToObject()).ToArray()
3272+
};
3273+
records.Add(ctx.UniqueId, record);
3274+
};
3275+
cfg.Interop.FunctionExecuted = ctx =>
3276+
{
3277+
if (records.TryGetValue(ctx.UniqueId, out var record) && ctx.Result is { Type: CompletionType.Return })
3278+
{
3279+
record.Result = ctx.Result.Value.Value.ToObject();
3280+
}
3281+
};
3282+
#endif
3283+
});
3284+
const string Js = @"
3285+
function main() {
3286+
return add(1, 2);
3287+
}
3288+
function add(a, b) {
3289+
return a + b;
3290+
}";
3291+
var script = Engine.PrepareScript(Js.TrimStart());
3292+
engine.Execute(script);
3293+
var result = engine.Invoke("main").ToObject();
3294+
Assert.Equal(3, Convert.ToInt32(result));
3295+
#if !NETFRAMEWORK
3296+
var traces = records.Values.ToList();
3297+
Assert.Equal(2, traces.Count);
3298+
Assert.Equal("main", traces[0].Name);
3299+
Assert.Equal(3, Convert.ToInt32(traces[0].Result));
3300+
Assert.Equal(2, traces[1].Args.Length);
3301+
Assert.Equal(1, Convert.ToInt32(traces[1].Args[0]));
3302+
Assert.Equal(2, Convert.ToInt32(traces[1].Args[1]));
3303+
Assert.Equal(1, traces[0].StartLine);
3304+
Assert.Equal(3, traces[0].EndLine);
3305+
#endif
3306+
}
3307+
3308+
private class FunctionCallRecord
3309+
{
3310+
public string Name { get; set; }
3311+
public int StartLine { get; set; }
3312+
public int EndLine { get; set; }
3313+
public object[] Args { get; set; }
3314+
public object Result { get; set; }
3315+
}
32543316
}
32553317
}

Jint/Options.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
using Jint.Native;
66
using Jint.Native.Object;
77
using Jint.Runtime;
8-
using Jint.Runtime.Interop;
8+
using Jint.Runtime.CallStack;
99
using Jint.Runtime.Debugger;
1010
using Jint.Runtime.Descriptors;
11+
using Jint.Runtime.Interop;
12+
using Jint.Runtime.Interop.Function;
1113
using Jint.Runtime.Modules;
12-
using Jint.Runtime.CallStack;
1314

1415
namespace Jint
1516
{
@@ -18,6 +19,11 @@ namespace Jint
1819
public delegate ObjectInstance? WrapObjectDelegate(Engine engine, object target);
1920

2021
public delegate bool ExceptionHandlerDelegate(Exception exception);
22+
#if !NETFRAMEWORK
23+
public delegate void FunctionExecutingDelegate(FunctionExecutionContext context);
24+
25+
public delegate void FunctionExecutedDelegate(FunctionExecutionContext context);
26+
#endif
2127

2228
public class Options
2329
{
@@ -333,7 +339,11 @@ public class InteropOptions
333339
/// <see cref="IObjectWrapper"/> passing through 'JSON.stringify'.
334340
/// </summary>
335341
public Func<object, string>? SerializeToJson { get; set; }
342+
#if !NETFRAMEWORK
343+
public FunctionExecutingDelegate? FunctionExecuting { get; set; }
336344

345+
public FunctionExecutedDelegate? FunctionExecuted { get; set; }
346+
#endif
337347
/// <summary>
338348
/// What kind of date time should be produced when JavaScript date is converted to DateTime. If Local, uses <see cref="Options.TimeZone"/>.
339349
/// Defaults to <see cref="System.DateTimeKind.Utc"/>.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Esprima.Ast;
2+
using Jint.Native;
3+
using Jint.Native.Function;
4+
5+
#nullable disable
6+
7+
namespace Jint.Runtime.Interop.Function;
8+
9+
public class FunctionExecutionContext
10+
{
11+
public Guid UniqueId { get; set; }
12+
public Engine Engine { get; set; }
13+
public IFunction Function { get; set; }
14+
public FunctionInstance FunctionInstance { get; set; }
15+
public JsValue[] Arguments { get; set; }
16+
public Completion? Result { get; set; }
17+
}

Jint/Runtime/Interpreter/JintFunctionDefinition.cs

+32-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Jint.Native.Function;
66
using Jint.Native.Promise;
77
using Jint.Runtime.Environments;
8+
using Jint.Runtime.Interop.Function;
89
using Jint.Runtime.Interpreter.Expressions;
910

1011
namespace Jint.Runtime.Interpreter;
@@ -38,6 +39,21 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
3839
{
3940
Completion result;
4041
ArgumentsInstance? argumentsInstance = null;
42+
#if !NETFRAMEWORK
43+
FunctionExecutionContext? executionContext = null;
44+
if (context.Engine.Options.Interop.FunctionExecuting != null)
45+
{
46+
executionContext = new FunctionExecutionContext
47+
{
48+
Engine = context.Engine,
49+
FunctionInstance = functionObject,
50+
Function = Function,
51+
Arguments = argumentsList,
52+
UniqueId = Guid.NewGuid()
53+
};
54+
context.Engine.Options.Interop.FunctionExecuting(executionContext);
55+
}
56+
#endif
4157
if (Function.Expression)
4258
{
4359
// https://tc39.es/ecma262/#sec-runtime-semantics-evaluateconcisebody
@@ -90,6 +106,21 @@ internal Completion EvaluateBody(EvaluationContext context, FunctionInstance fun
90106
}
91107

92108
argumentsInstance?.FunctionWasCalled();
109+
#if !NETFRAMEWORK
110+
if (context.Engine.Options.Interop.FunctionExecuted != null)
111+
{
112+
executionContext ??= new FunctionExecutionContext
113+
{
114+
Engine = context.Engine,
115+
Function = Function,
116+
FunctionInstance = functionObject,
117+
Arguments = argumentsList,
118+
UniqueId = Guid.NewGuid()
119+
};
120+
executionContext.Result = result;
121+
context.Engine.Options.Interop.FunctionExecuted(executionContext);
122+
}
123+
#endif
93124
return result;
94125
}
95126

@@ -415,7 +446,7 @@ private static void ProcessParameters(
415446
out bool hasArguments)
416447
{
417448
hasArguments = false;
418-
state.IsSimpleParameterList = true;
449+
state.IsSimpleParameterList = true;
419450

420451
var countParameters = true;
421452
ref readonly var functionDeclarationParams = ref function.Params;

0 commit comments

Comments
 (0)