using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using Jint.Collections; using Jint.Native.Function; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions; using Environment = Jint.Runtime.Environments.Environment; namespace Jint.Runtime.CallStack; // smaller version with only required info internal readonly record struct CallStackExecutionContext { public CallStackExecutionContext(in ExecutionContext context) { LexicalEnvironment = context.LexicalEnvironment; } internal readonly Environment LexicalEnvironment; internal Environment GetThisEnvironment() { var lex = LexicalEnvironment; while (true) { if (lex is not null) { if (lex.HasThisBinding()) { return lex; } lex = lex._outerEnv; } } } } internal sealed class JintCallStack { private readonly RefStack _stack = new(); private readonly Dictionary? _statistics; // Internal for use by DebugHandler internal RefStack Stack => _stack; public JintCallStack(bool trackRecursionDepth) { if (trackRecursionDepth) { _statistics = new Dictionary(CallStackElementComparer.Instance); } } public int Push(Function function, JintExpression? expression, in ExecutionContext executionContext) { var item = new CallStackElement(function, expression, new CallStackExecutionContext(executionContext)); _stack.Push(item); if (_statistics is not null) { #pragma warning disable CA1854 #pragma warning disable CA1864 if (_statistics.ContainsKey(item)) #pragma warning restore CA1854 #pragma warning restore CA1864 { return ++_statistics[item]; } else { _statistics.Add(item, 0); return 0; } } return -1; } public CallStackElement Pop() { ref readonly var item = ref _stack.Pop(); if (_statistics is not null) { if (_statistics[item] == 0) { _statistics.Remove(item); } else { _statistics[item]--; } } return item; } public bool TryPeek([NotNullWhen(true)] out CallStackElement item) { return _stack.TryPeek(out item); } public int Count => _stack._size; public void Clear() { _stack.Clear(); _statistics?.Clear(); } public override string ToString() { return string.Join("->", _stack.Select(static cse => cse.ToString()).Reverse()); } internal string BuildCallStackString(Engine engine, SourceLocation location, int excludeTop = 0) { static void AppendLocation( ref ValueStringBuilder sb, string shortDescription, in SourceLocation loc, in CallStackElement? element, Options.BuildCallStackDelegate? callStackBuilder) { if (callStackBuilder != null && TryInvokeCustomCallStackHandler(callStackBuilder, element, shortDescription, loc, ref sb)) { return; } var hasShortDescription = !string.IsNullOrWhiteSpace(shortDescription); sb.Append(" at "); if (hasShortDescription) { sb.Append(shortDescription); sb.Append(" ("); } sb.Append(loc.SourceFile); sb.Append(':'); sb.Append(loc.End.Line); sb.Append(':'); sb.Append(loc.Start.Column + 1); // report column number instead of index if (hasShortDescription) { sb.Append(')'); } sb.Append(System.Environment.NewLine); } var customCallStackBuilder = engine.Options.Interop.BuildCallStackHandler; var builder = new ValueStringBuilder(); // stack is one frame behind function-wise when we start to process it from expression level var index = _stack._size - 1 - excludeTop; var element = index >= 0 ? _stack[index] : (CallStackElement?) null; var shortDescription = element?.ToString() ?? ""; AppendLocation(ref builder, shortDescription, location, element, customCallStackBuilder); location = element?.Location ?? default; index--; while (index >= -1) { element = index >= 0 ? _stack[index] : null; shortDescription = element?.ToString() ?? ""; AppendLocation(ref builder, shortDescription, location, element, customCallStackBuilder); location = element?.Location ?? default; index--; } var result = builder.AsSpan().TrimEnd().ToString(); builder.Dispose(); return result; } private static bool TryInvokeCustomCallStackHandler( Options.BuildCallStackDelegate handler, CallStackElement? element, string shortDescription, SourceLocation loc, ref ValueStringBuilder sb) { string[]? arguments = null; if (element?.Arguments is not null) { var args = element.Value.Arguments.Value; arguments = args.Count > 0 ? new string[args.Count] : []; for (var i = 0; i < arguments.Length; i++) { arguments[i] = GetPropertyKey(args[i]); } } var str = handler(shortDescription, loc, arguments); if (!string.IsNullOrEmpty(str)) { sb.Append(str); return true; } return false; } /// /// A version of that cannot get into loop as we are already building a stack. /// private static string GetPropertyKey(Node expression) { if (expression is Literal literal) { return AstExtensions.LiteralKeyToString(literal); } if (expression is Identifier identifier) { return identifier.Name ?? ""; } if (expression is MemberExpression { Computed: false } staticMemberExpression) { return $"{GetPropertyKey(staticMemberExpression.Object)}.{GetPropertyKey(staticMemberExpression.Property)}"; } return "?"; } }