using System.Diagnostics.CodeAnalysis; using System.Globalization; 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(SourceLocation location, int excludeTop = 0) { static void AppendLocation( ref ValueStringBuilder sb, string shortDescription, in SourceLocation loc, in CallStackElement? element) { sb.Append(" at"); if (!string.IsNullOrWhiteSpace(shortDescription)) { sb.Append(' '); sb.Append(shortDescription); } 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(')'); } sb.Append(' '); sb.Append(loc.SourceFile); 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); } 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); location = element?.Location ?? default; index--; while (index >= -1) { element = index >= 0 ? _stack[index] : null; shortDescription = element?.ToString() ?? ""; AppendLocation(ref builder, shortDescription, location, element); location = element?.Location ?? default; index--; } var result = builder.AsSpan().TrimEnd().ToString(); builder.Dispose(); return result; } /// /// 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 "?"; } } }