diff --git a/Jint/Engine.Advanced.cs b/Jint/Engine.Advanced.cs index 61b8672a0d..2bd8aace37 100644 --- a/Jint/Engine.Advanced.cs +++ b/Jint/Engine.Advanced.cs @@ -28,7 +28,7 @@ public string StackTrace return string.Empty; } - return _engine.CallStack.BuildCallStackString(lastSyntaxElement.Location); + return _engine.CallStack.BuildCallStackString(_engine, lastSyntaxElement.Location); } } diff --git a/Jint/Native/Error/ErrorConstructor.cs b/Jint/Native/Error/ErrorConstructor.cs index a03b7382d4..1db1d25cc2 100644 --- a/Jint/Native/Error/ErrorConstructor.cs +++ b/Jint/Native/Error/ErrorConstructor.cs @@ -80,7 +80,7 @@ public override ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) // If the current function is the ErrorConstructor itself (i.e. "throw new Error(...)" was called // from script), exclude it from the stack trace, because the trace should begin at the throw point. - return callStack.BuildCallStackString(lastSyntaxNode.Location, currentFunction == this ? 1 : 0); + return callStack.BuildCallStackString(_engine, lastSyntaxNode.Location, currentFunction == this ? 1 : 0); } } } diff --git a/Jint/Options.Extensions.cs b/Jint/Options.Extensions.cs index 86822dcae0..115c5e5708 100644 --- a/Jint/Options.Extensions.cs +++ b/Jint/Options.Extensions.cs @@ -127,6 +127,17 @@ public static Options SetWrapObjectHandler(this Options options, Options.WrapObj return options; } + /// + /// + /// + /// + /// + public static Options SetBuildCallStackHandler(this Options options, Options.BuildCallStackDelegate buildCallStackHandler) + { + options.Interop.BuildCallStackHandler = buildCallStackHandler; + return options; + } + /// /// Sets the type converter to use. /// diff --git a/Jint/Options.cs b/Jint/Options.cs index c786d53399..0bf448e73e 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Text; +using Esprima; using Jint.Native; using Jint.Native.Object; using Jint.Runtime; @@ -27,6 +29,8 @@ public class Options public delegate bool ExceptionHandlerDelegate(Exception exception); + public delegate void BuildCallStackDelegate(ref ValueStringBuilder valueStringBuilder, string shortDescription, Location location, List? arguments); + /// /// Execution constraints for the engine. /// @@ -293,6 +297,42 @@ public class InteropOptions /// public WrapObjectDelegate WrapObjectHandler { get; set; } = static (engine, target, type) => new ObjectWrapper(engine, target, type); + /// + /// + /// + public BuildCallStackDelegate BuildCallStackHandler { get; set; } = static (ref ValueStringBuilder sb, string shortDescription, Location loc, List? arguments) => + { + sb.Append(" at"); + + if (!string.IsNullOrWhiteSpace(shortDescription)) + { + sb.Append(' '); + sb.Append(shortDescription); + } + + if (arguments is not null) + { + sb.Append(" ("); + for (var index = 0; index < arguments.Count; index++) + { + if (index != 0) + { + sb.Append(", "); + } + sb.Append(arguments[index]); + } + sb.Append(')'); + } + + sb.Append(' '); + sb.Append(loc.Source); + sb.Append(':'); + sb.Append(loc.End.Line.ToString(CultureInfo.InvariantCulture)); + sb.Append(':'); + sb.Append((loc.Start.Column + 1).ToString(CultureInfo.InvariantCulture)); // report column number instead of index + sb.Append(Environment.NewLine); + }; + /// /// /// diff --git a/Jint/Pooling/ValueStringBuilder.cs b/Jint/Pooling/ValueStringBuilder.cs index f5bd679329..3667b6489b 100644 --- a/Jint/Pooling/ValueStringBuilder.cs +++ b/Jint/Pooling/ValueStringBuilder.cs @@ -9,7 +9,7 @@ // ReSharper disable once CheckNamespace namespace System.Text; -internal ref struct ValueStringBuilder +public ref struct ValueStringBuilder { private char[]? _arrayToReturnToPool; private Span _chars; diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index d4b26749bd..373e9254a1 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -117,46 +117,23 @@ public override string ToString() return string.Join("->", _stack.Select(static cse => cse.ToString()).Reverse()); } - internal string BuildCallStackString(Location location, int excludeTop = 0) + internal string BuildCallStackString(Engine engine, Location location, int excludeTop = 0) { static void AppendLocation( ref ValueStringBuilder sb, string shortDescription, in Location loc, - in CallStackElement? element) + in CallStackElement? element, + Engine engine) { - sb.Append(" at"); - - if (!string.IsNullOrWhiteSpace(shortDescription)) - { - sb.Append(' '); - sb.Append(shortDescription); - } + List? arguments = null; if (element?.Arguments is not null) { - // it's a function - sb.Append(" ("); - for (var index = 0; index < element.Value.Arguments.Value.Count; index++) - { - if (index != 0) - { - sb.Append(", "); - } - - var arg = element.Value.Arguments.Value[index]; - sb.Append(GetPropertyKey(arg)); - } - sb.Append(')'); + arguments = element.Value.Arguments.Value.Select(GetPropertyKey).ToList(); } - sb.Append(' '); - sb.Append(loc.Source); - sb.Append(':'); - sb.Append(loc.End.Line.ToString(CultureInfo.InvariantCulture)); - sb.Append(':'); - sb.Append((loc.Start.Column + 1).ToString(CultureInfo.InvariantCulture)); // report column number instead of index - sb.Append(System.Environment.NewLine); + engine.Options.Interop.BuildCallStackHandler.Invoke(ref sb, shortDescription, loc, arguments); } var builder = new ValueStringBuilder(); @@ -166,7 +143,7 @@ static void AppendLocation( var element = index >= 0 ? _stack[index] : (CallStackElement?) null; var shortDescription = element?.ToString() ?? ""; - AppendLocation(ref builder, shortDescription, location, element); + AppendLocation(ref builder, shortDescription, location, element, engine); location = element?.Location ?? default; index--; @@ -176,7 +153,7 @@ static void AppendLocation( element = index >= 0 ? _stack[index] : null; shortDescription = element?.ToString() ?? ""; - AppendLocation(ref builder, shortDescription, location, element); + AppendLocation(ref builder, shortDescription, location, element, engine); location = element?.Location ?? default; index--; diff --git a/Jint/Runtime/JavaScriptException.cs b/Jint/Runtime/JavaScriptException.cs index d79b546062..68c8a6cd81 100644 --- a/Jint/Runtime/JavaScriptException.cs +++ b/Jint/Runtime/JavaScriptException.cs @@ -89,7 +89,7 @@ internal void SetCallstack(Engine engine, Location location, bool overwriteExist var errObj = Error.IsObject() ? Error.AsObject() : null; if (errObj is null) { - _callStack = engine.CallStack.BuildCallStackString(location); + _callStack = engine.CallStack.BuildCallStackString(engine, location); return; } @@ -100,7 +100,7 @@ internal void SetCallstack(Engine engine, Location location, bool overwriteExist } else { - _callStack = engine.CallStack.BuildCallStackString(location); + _callStack = engine.CallStack.BuildCallStackString(engine, location); errObj.FastSetProperty(CommonProperties.Stack._value, new PropertyDescriptor(_callStack, false, false, false)); } }