Explorar o código

Stack traces for JS exceptions (#821)

* improve stack trace printing and make it follow node/chromium more
* harmonize JavaScriptException to look more line a .NET exception
* handle getter/setter in recursion limits
* add a public way to construct reflection property descriptors
Marko Lahma %!s(int64=4) %!d(string=hai) anos
pai
achega
de3739bd3d

+ 22 - 13
Jint.Repl/Program.cs

@@ -2,26 +2,28 @@
 using System.Diagnostics;
 using System.IO;
 using System.Reflection;
+using Esprima;
 using Jint.Native;
 using Jint.Runtime;
 
 namespace Jint.Repl
 {
-    class Program
+    internal static class Program
     {
-        static void Main(string[] args)
+        private static void Main(string[] args)
         {
-
-            var engine = new Engine(cfg => cfg.AllowClr());
+            var engine = new Engine(cfg => cfg
+                .AllowClr()
+            );
 
             engine
                 .SetValue("print", new Action<object>(Console.WriteLine))
                 .SetValue("load", new Func<string, object>(
-                    path => engine.Execute(File.ReadAllText(path))
-                                  .GetCompletionValue()));
+                    path => engine.Execute(File.ReadAllText(path)).GetCompletionValue())
+                );
 
             var filename = args.Length > 0 ? args[0] : "";
-            if (!String.IsNullOrEmpty(filename))
+            if (!string.IsNullOrEmpty(filename))
             {
                 if (!File.Exists(filename))
                 {
@@ -33,9 +35,9 @@ namespace Jint.Repl
                 return;
             }
 
-            Assembly assembly = Assembly.GetExecutingAssembly();
-            FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
-            string version = fvi.FileVersion;
+            var assembly = Assembly.GetExecutingAssembly();
+            var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
+            var version = fvi.FileVersion;
 
             Console.WriteLine("Welcome to Jint ({0})", version);
             Console.WriteLine("Type 'exit' to leave, " +
@@ -44,6 +46,14 @@ namespace Jint.Repl
             Console.WriteLine();
 
             var defaultColor = Console.ForegroundColor;
+            var parserOptions = new ParserOptions("repl")
+            {
+                Loc = true,
+                Range = true,
+                Tolerant = true,
+                AdaptRegexp = true
+            };
+
             while (true)
             {
                 Console.ForegroundColor = defaultColor;
@@ -56,7 +66,7 @@ namespace Jint.Repl
 
                 try
                 {
-                    var result = engine.GetValue(engine.Execute(input).GetCompletionValue());
+                    var result = engine.GetValue(engine.Execute(input, parserOptions).GetCompletionValue());
                     if (result.Type != Types.None && result.Type != Types.Null && result.Type != Types.Undefined)
                     {
                         var str = TypeConverter.ToString(engine.Json.Stringify(engine.Json, Arguments.From(result, Undefined.Instance, "  ")));
@@ -73,8 +83,7 @@ namespace Jint.Repl
                     Console.ForegroundColor = ConsoleColor.Red;
                     Console.WriteLine(e.Message);
                 }
-
             }
         }
     }
-}
+}

+ 3 - 1
Jint.Tests.Ecma/EcmaTest.cs

@@ -197,7 +197,9 @@ namespace Jint.Tests.Ecma
 
             //NOTE: The Date tests in test262 assume the local timezone is Pacific Standard Time
             var pacificTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
-            var engine = new Engine(cfg => cfg.LocalTimeZone(pacificTimeZone));
+            var engine = new Engine(cfg => cfg
+                .LocalTimeZone(pacificTimeZone)
+            );
 
             // loading driver
             if (staSource == null)

+ 2 - 0
Jint.Tests.Test262/Jint.Tests.Test262.csproj

@@ -1,6 +1,8 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>net461;net5.0</TargetFrameworks>
+    <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
+    <SignAssembly>true</SignAssembly>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />

+ 5 - 9
Jint.Tests.Test262/Test262Test.cs

@@ -10,8 +10,8 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Newtonsoft.Json.Linq;
-using Xunit;
 using Xunit.Abstractions;
+using Xunit.Sdk;
 
 namespace Jint.Tests.Test262
 {
@@ -100,7 +100,7 @@ namespace Jint.Tests.Test262
                 var parser = new JavaScriptParser(args.At(0).AsString(), options);
                 var script = parser.ParseScript(strict);
 
-                var value = engine.Execute(script).GetCompletionValue();
+                var value = engine.Execute(script, false).GetCompletionValue();
                 
                 return value;
             }), true, true, true));
@@ -130,20 +130,16 @@ namespace Jint.Tests.Test262
             }
             catch (JavaScriptException j)
             {
-                lastError = TypeConverter.ToString(j.Error);
+                lastError = j.ToString();
             }
             catch (Exception e)
             {
                 lastError = e.ToString();
             }
 
-            if (negative)
+            if (!negative && !string.IsNullOrWhiteSpace(lastError))
             {
-                Assert.NotNull(lastError);
-            }
-            else
-            {
-                Assert.Null(lastError);
+                throw new XunitException(lastError);
             }
         }
 

+ 31 - 0
Jint.Tests/Runtime/EngineTests.cs

@@ -937,6 +937,37 @@ namespace Jint.Tests.Runtime
             Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(input));
         }
 
+        [Fact]
+        public void ShouldLimitRecursionWithAllFunctionInstances()
+        {
+            var engine = new Engine(cfg =>
+            {
+                // Limit recursion to 5 invocations
+                cfg.LimitRecursion(5);
+                cfg.Strict();
+            });
+
+            var ex = Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(@"
+var myarr = new Array(5000);
+for (var i = 0; i < myarr.length; i++) {
+    myarr[i] = function(i) {
+        myarr[i + 1](i + 1);
+    }
+}
+
+myarr[0](0);
+"));
+        }
+
+        [Fact]
+        public void ShouldLimitRecursionWithGetters()
+        {
+            const string code = @"var obj = { get test() { return this.test + '2';  } }; obj.test;";
+            var engine = new Engine(cfg => cfg.LimitRecursion(10));
+            
+            Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(code).GetCompletionValue());
+        }
+
         [Fact]
         public void ShouldConvertDoubleToStringWithoutLosingPrecision()
         {

+ 50 - 25
Jint.Tests/Runtime/ErrorTests.cs

@@ -11,7 +11,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation1()
         {
-            var script = @"
+            const string script = @"
 var a = {};
 
 var b = a.user.name;
@@ -21,34 +21,28 @@ var b = a.user.name;
             var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
             Assert.Equal("Cannot read property 'name' of undefined", e.Message);
             Assert.Equal(4, e.Location.Start.Line);
-            Assert.Equal(8, e.Location.Start.Column);
+            Assert.Equal(15, e.Location.Start.Column);
         }
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation1WithoutReferencedName()
         {
-            var script = @"
+            const string script = @"
 var c = a(b().Length);
 ";
 
             var engine = new Engine();
-            engine.SetValue("a", new Action<string>((a) =>
-            {
-
-            }));
-            engine.SetValue("b", new Func<string>(() =>
-            {
-                return null;
-            }));
+            engine.SetValue("a", new Action<string>((_) => { }));
+            engine.SetValue("b", new Func<string>(() => null));
             var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
             Assert.Equal("Cannot read property 'Length' of null", e.Message);
             Assert.Equal(2, e.Location.Start.Line);
-            Assert.Equal(10, e.Location.Start.Column);
+            Assert.Equal(14, e.Location.Start.Column);
         }
 
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation2()
         {
-            var script = @"
+            const string script = @"
  test();
 ";
 
@@ -62,10 +56,10 @@ var c = a(b().Length);
         [Fact]
         public void CanProduceCorrectStackTrace()
         {
-            var engine = new Engine(options => options.LimitRecursion(1000));
+            var engine = new Engine();
 
             engine.Execute(@"var a = function(v) {
-	return v.xxx.yyy;
+    return v.xxx.yyy;
 }
 
 var b = function(v) {
@@ -75,13 +69,13 @@ var b = function(v) {
             var e = Assert.Throws<JavaScriptException>(() => engine.Execute("var x = b(7);", new ParserOptions("main.js") { Loc = true } ));
             Assert.Equal("Cannot read property 'yyy' of undefined", e.Message);
             Assert.Equal(2, e.Location.Start.Line);
-            Assert.Equal(8, e.Location.Start.Column);
+            Assert.Equal(17, e.Location.Start.Column);
             Assert.Equal("custom.js", e.Location.Source);
 
-            var stack = e.CallStack;
-            Assert.Equal(@" at a(v) @ custom.js 8:6
- at b(7) @ main.js 8:1
-".Replace("\r\n", "\n"), stack.Replace("\r\n", "\n"));
+            var stack = e.StackTrace;
+            EqualIgnoringNewLineDifferences(@"    at a (v) custom.js:2:18
+    at b (v) custom.js:6:9
+    at main.js:1:9", stack);
         }
 
         private class Folder
@@ -128,11 +122,11 @@ var b = function(v) {
             ));
 
             Assert.Equal("Cannot read property 'Name' of null", javaScriptException.Message);
-            Assert.Equal(@" at recursive(folderInstance.parent) @  31:8
- at recursive(folderInstance.parent) @  31:8
- at recursive(folderInstance.parent) @  31:8
- at recursive(folder) @  16:12
-", javaScriptException.CallStack);
+            EqualIgnoringNewLineDifferences(@"    at recursive (folderInstance) <anonymous>:6:44
+    at recursive (folderInstance) <anonymous>:8:32
+    at recursive (folderInstance) <anonymous>:8:32
+    at recursive (folderInstance) <anonymous>:8:32
+    at <anonymous>:12:17", javaScriptException.StackTrace);
 
             var expected = new List<string>
             {
@@ -140,5 +134,36 @@ var b = function(v) {
             };
             Assert.Equal(expected, recordedFolderTraversalOrder);
         }
+
+        [Fact]
+        public void StackTraceCollectedOnThreeLevels()
+        {
+            var engine = new Engine();
+            const string script = @"var a = function(v) {
+    return v.xxx.yyy;
+}
+
+var b = function(v) {
+    return a(v);
+}
+
+var x = b(7);";
+
+            var ex = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
+
+            const string expected = @"Jint.Runtime.JavaScriptException: Cannot read property 'yyy' of undefined
+    at a (v) <anonymous>:2:18
+    at b (v) <anonymous>:6:12
+    at <anonymous>:9:9";
+            
+            EqualIgnoringNewLineDifferences(expected, ex.ToString());
+        }
+
+        private static void EqualIgnoringNewLineDifferences(string expected, string actual)
+        {
+            expected = expected.Replace("\r\n", "\n");
+            actual = actual.Replace("\r\n", "\n");
+            Assert.Equal(expected, actual);
+        }
     }
 }

+ 1 - 0
Jint/AssemblyInfoExtras.cs

@@ -1,4 +1,5 @@
 using System.Runtime.CompilerServices;
 
 [assembly: InternalsVisibleTo("Jint.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]
+[assembly: InternalsVisibleTo("Jint.Tests.Test262, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]
 [assembly: InternalsVisibleTo("Jint.Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]

+ 198 - 0
Jint/Collections/RefStack.cs

@@ -0,0 +1,198 @@
+#nullable enable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Jint.Runtime;
+
+namespace Jint.Collections
+{
+    /// <summary>
+    /// Stack for struct types.
+    /// </summary>
+    internal sealed class RefStack<T> : IEnumerable<T> where T : struct
+    {
+        internal T[] _array;
+        internal int _size;
+
+        private const int DefaultCapacity = 2;
+
+        public RefStack(int capacity = DefaultCapacity)
+        {
+            _array = new T[capacity];
+            _size = 0;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly T Peek()
+        {
+            if (_size == 0)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
+            }
+
+            return ref _array[_size - 1];
+        }
+
+        public T this[int index] => _array[index];
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryPeek([NotNullWhen(true)] out T item)
+        {
+            if (_size > 0)
+            {
+                item = _array[_size - 1];
+                return true;
+            }
+
+            item = default;
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly T Pop()
+        {
+            if (_size == 0)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
+            }
+
+            _size--;
+            return ref _array[_size];
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Push(in T item)
+        {
+            if (_size == _array.Length)
+            {
+                EnsureCapacity(_size + 1);
+            }
+
+            _array[_size++] = item;
+        }
+
+        private void EnsureCapacity(int min)
+        {
+            var array = _array;
+            if (array.Length < min)
+            {
+                var newCapacity = array.Length == 0
+                    ? DefaultCapacity
+                    : array.Length * 2;
+
+                if (newCapacity < min)
+                {
+                    newCapacity = min;
+                }
+
+                Resize(newCapacity);
+            }
+        }
+
+        private void Resize(int value)
+        {
+            if (value != _array.Length)
+            {
+                if (value > 0)
+                {
+                    var newItems = new T[value];
+                    if (_size > 0)
+                    {
+                        Array.Copy(_array, 0, newItems, 0, _size);
+                    }
+
+                    _array = newItems;
+                }
+                else
+                {
+                    _array = Array.Empty<T>();
+                }
+            }
+        }
+
+        public void Clear()
+        {
+            _size = 0;
+        }
+
+        public Enumerator GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        internal struct Enumerator : IEnumerator<T>
+        {
+            private readonly RefStack<T> _stack;
+            private int _index;
+            private T? _currentElement;
+
+            internal Enumerator(RefStack<T> stack)
+            {
+                _stack = stack;
+                _index = -2;
+                _currentElement = default;
+            }
+
+            public void Dispose()
+            {
+                _index = -1;
+            }
+
+            public bool MoveNext()
+            {
+                bool returnValue;
+                if (_index == -2)
+                { 
+                    // First call to enumerator.
+                    _index = _stack._size - 1;
+                    returnValue = (_index >= 0);
+                    if (returnValue)
+                    {
+                        _currentElement = _stack._array[_index];
+                    }
+                    return returnValue;
+                }
+
+                if (_index == -1)
+                { 
+                    // End of enumeration.
+                    return false;
+                }
+
+                returnValue = (--_index >= 0);
+                if (returnValue)
+                {
+                    _currentElement = _stack._array[_index];
+                }
+                else
+                {
+                    _currentElement = default;
+                }
+                return returnValue;
+            }
+
+            public T Current => (T) _currentElement!;
+
+            object? IEnumerator.Current => Current;
+
+            void IEnumerator.Reset()
+            {
+                _index = -2;
+                _currentElement = default;
+            }        
+        }
+    }
+}

+ 66 - 13
Jint/Engine.cs

@@ -38,14 +38,13 @@ namespace Jint
 {
     public class Engine
     {
-        private static readonly ParserOptions DefaultParserOptions = new ParserOptions
+        private static readonly ParserOptions DefaultParserOptions = new("<anonymous>")
         {
             AdaptRegexp = true,
             Tolerant = true,
             Loc = true
         };
 
-
         private static readonly JsString _errorFunctionName = new JsString("Error");
         private static readonly JsString _evalErrorFunctionName = new JsString("EvalError");
         private static readonly JsString _rangeErrorFunctionName = new JsString("RangeError");
@@ -110,7 +109,7 @@ namespace Jint
 
         internal static Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor> ReflectionAccessors = new();
 
-        internal readonly JintCallStack CallStack = new JintCallStack();
+        internal readonly JintCallStack CallStack;
 
         /// <summary>
         /// Constructs a new engine instance.
@@ -194,6 +193,7 @@ namespace Jint
             _isStrict = Options.IsStrict;
             _constraints = Options._Constraints;
             _referenceResolver = Options.ReferenceResolver;
+            CallStack = new JintCallStack(Options.MaxRecursionDepth >= 0);
 
             _referencePool = new ReferencePool();
             _argumentsInstancePool = new ArgumentsInstancePool(this);
@@ -347,19 +347,27 @@ namespace Jint
             return Execute(parser.ParseScript());
         }
 
-        public Engine Execute(Script program)
+        public Engine Execute(Script script)
+        {
+            return Execute(script, true);
+        }
+
+        internal Engine Execute(Script script, bool resetState)
         {
-            ResetConstraints();
-            ResetLastStatement();
-            ResetCallStack();
+            if (resetState)
+            {
+                ResetConstraints();
+                ResetLastStatement();
+                ResetCallStack();
+            }
 
-            using (new StrictModeScope(_isStrict || program.Strict))
+            using (new StrictModeScope(_isStrict || script.Strict))
             {
                 GlobalDeclarationInstantiation(
-                    program,
+                    script,
                     GlobalEnvironment);
 
-                var list = new JintStatementList(this, null, program.Body);
+                var list = new JintStatementList(this, null, script.Body);
                 
                 var result = list.Execute();
                 if (result.Type == CompletionType.Throw)
@@ -658,9 +666,9 @@ namespace Jint
         }
 
         /// <summary>
-        /// Gets the last evaluated <see cref="INode"/>.
+        /// Gets the last evaluated <see cref="Node"/>.
         /// </summary>
-        public Node GetLastSyntaxNode()
+        internal Node GetLastSyntaxNode()
         {
             return _lastSyntaxNode;
         }
@@ -687,7 +695,7 @@ namespace Jint
             return GetIdentifierReference(env, name, StrictModeScope.IsStrictModeCode);
         }
 
-        private Reference GetIdentifierReference(LexicalEnvironment lex, string name, in bool strict)
+        private Reference GetIdentifierReference(LexicalEnvironment lex, string name, bool strict)
         {
             if (lex is null)
             {
@@ -1192,5 +1200,50 @@ namespace Jint
         {
             _executionContexts.ReplaceTopVariableEnvironment(newEnv);
         }
+
+        internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, Location? location)
+        {
+            if (callable is FunctionInstance functionInstance)
+            {
+                return Call(functionInstance, thisObject, arguments, location);
+            }
+            
+            return callable.Call(thisObject, arguments);
+        }
+
+        internal JsValue Call(
+            FunctionInstance functionInstance,
+            JsValue thisObject,
+            JsValue[] arguments,
+            Location? location)
+        {
+            location ??= ((Node) functionInstance._functionDefinition?.Function)?.Location;
+
+            var callStackElement = new CallStackElement(functionInstance, location);
+            var recursionDepth = CallStack.Push(callStackElement);
+
+            if (recursionDepth > Options.MaxRecursionDepth)
+            {
+                // pop the current element as it was never reached
+                CallStack.Pop();
+                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString());
+            }
+
+            if (_isDebugMode)
+            {
+                DebugHandler.AddToDebugCallStack(functionInstance);
+            }
+
+            var result = functionInstance.Call(thisObject, arguments);
+
+            if (_isDebugMode)
+            {
+                DebugHandler.PopDebugCallStack();
+            }
+
+            CallStack.Pop();
+
+            return result;
+        }
     }
 }

+ 1 - 0
Jint/Jint.csproj

@@ -8,5 +8,6 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Esprima" Version="2.0.0-beta-1331" />
+    <PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
   </ItemGroup>
 </Project>

+ 0 - 1
Jint/Native/Json/JsonSerializer.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Linq;
-using Jint.Native.Array;
 using Jint.Native.Global;
 using Jint.Native.Object;
 using Jint.Runtime;

+ 4 - 4
Jint/Native/Object/ObjectInstance.cs

@@ -341,9 +341,8 @@ namespace Jint.Native.Object
                 return Undefined;
             }
 
-            // if getter is not undefined it must be ICallable
-            var callable = getter.TryCast<ICallable>();
-            return callable.Call(thisObject, Arguments.Empty);
+            var functionInstance = (FunctionInstance) getter;
+            return functionInstance._engine.Call(functionInstance, thisObject, Arguments.Empty, location: null);
         }
 
         /// <summary>
@@ -501,7 +500,8 @@ namespace Jint.Native.Object
                 return false;
             }
 
-            setter.Call(receiver, new[] {value});
+            var functionInstance = (FunctionInstance) setter;
+            _engine.Call(functionInstance, receiver, new[] { value }, location: null);
 
             return true;
         }

+ 1 - 1
Jint/Pooling/StringBuilderPool.cs

@@ -9,7 +9,7 @@ namespace Jint.Pooling
     /// <summary>
     /// Pooling of StringBuilder instances.
     /// </summary>
-    internal sealed class StringBuilderPool
+    internal static class StringBuilderPool
     {
         private static readonly ConcurrentObjectPool<StringBuilder> _pool;
 

+ 11 - 13
Jint/Runtime/CallStack/CallStackElement.cs

@@ -1,26 +1,24 @@
-using Esprima.Ast;
-using Jint.Native;
+using Esprima;
+using Jint.Native.Function;
 
-namespace Jint.Runtime
+namespace Jint.Runtime.CallStack
 {
-    public class CallStackElement
+    internal readonly struct CallStackElement
     {
-        private readonly string _shortDescription;
-
-        public CallStackElement(CallExpression callExpression, JsValue function, string shortDescription)
+        public CallStackElement(
+            FunctionInstance function,
+            Location? location)
         {
-            _shortDescription = shortDescription;
-            CallExpression = callExpression;
             Function = function;
+            Location = location;
         }
 
-        public CallExpression CallExpression { get; }
-
-        public JsValue Function { get; }
+        public readonly FunctionInstance Function;
+        public readonly Location? Location;
 
         public override string ToString()
         {
-            return _shortDescription;
+            return TypeConverter.ToString(Function?.Get(CommonProperties.Name));
         }
     }
 }

+ 23 - 6
Jint/Runtime/CallStack/CallStackElementComparer.cs

@@ -1,16 +1,33 @@
-namespace Jint.Runtime.CallStack
-{
-    using System.Collections.Generic;
+#nullable enable
+
+using System.Collections.Generic;
 
-    public class CallStackElementComparer: IEqualityComparer<CallStackElement>
+namespace Jint.Runtime.CallStack
+{
+    internal sealed class CallStackElementComparer: IEqualityComparer<CallStackElement>
     {
+        public static readonly CallStackElementComparer Instance = new();
+        
+        private CallStackElementComparer()
+        {
+        }
+
         public bool Equals(CallStackElement x, CallStackElement y)
         {
-            return ReferenceEquals(x?.Function, y?.Function);
+            if (x.Function._functionDefinition is not null)
+            {
+                return ReferenceEquals(x.Function._functionDefinition, y.Function._functionDefinition);
+            }
+            
+            return ReferenceEquals(x.Function, y.Function);
         }
 
         public int GetHashCode(CallStackElement obj)
-        {
+        {           
+            if (obj.Function._functionDefinition is not null)
+            {
+                return obj.Function._functionDefinition.GetHashCode();
+            }
             return obj.Function.GetHashCode();
         }
     }

+ 132 - 29
Jint/Runtime/CallStack/JintCallStack.cs

@@ -1,41 +1,60 @@
-using System.Collections;
+#nullable enable
+
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
+using Esprima;
+using Esprima.Ast;
+using Jint.Collections;
+using Jint.Pooling;
 
 namespace Jint.Runtime.CallStack
 {
-
-    public class JintCallStack : IEnumerable<CallStackElement>
+    internal class JintCallStack
     {
-        private readonly Stack<CallStackElement> _stack = new Stack<CallStackElement>();
-
-        private readonly Dictionary<CallStackElement, int> _statistics =
-            new Dictionary<CallStackElement, int>(new CallStackElementComparer());
+        private readonly RefStack<CallStackElement> _stack = new();
+        private readonly Dictionary<CallStackElement, int>? _statistics;
 
-        public int Push(CallStackElement item)
+        public JintCallStack(bool trackRecursionDepth)
         {
-            _stack.Push(item);
-            if (_statistics.ContainsKey(item))
+            if (trackRecursionDepth)
             {
-                return ++_statistics[item];
+                _statistics = new Dictionary<CallStackElement, int>(CallStackElementComparer.Instance);
             }
-            else
+        }
+
+        public int Push(in CallStackElement item)
+        {
+            _stack.Push(item);
+            if (_statistics is not null)
             {
-                _statistics.Add(item, 0);
-                return 0;
+                if (_statistics.ContainsKey(item))
+                {
+                    return ++_statistics[item];
+                }
+                else
+                {
+                    _statistics.Add(item, 0);
+                    return 0;
+                }
             }
+
+            return -1;
         }
 
         public CallStackElement Pop()
         {
-            var item = _stack.Pop();
-            if (_statistics[item] == 0)
-            {
-                _statistics.Remove(item);
-            }
-            else
+            ref readonly var item = ref _stack.Pop();
+            if (_statistics is not null)
             {
-                _statistics[item]--;
+                if (_statistics[item] == 0)
+                {
+                    _statistics.Remove(item);
+                }
+                else
+                {
+                    _statistics[item]--;
+                }
             }
 
             return item;
@@ -44,22 +63,106 @@ namespace Jint.Runtime.CallStack
         public void Clear()
         {
             _stack.Clear();
-            _statistics.Clear();
+            _statistics?.Clear();
         }
 
-        public IEnumerator<CallStackElement> GetEnumerator()
+        public override string ToString()
         {
-            return _stack.GetEnumerator();
+            return string.Join("->", _stack.Select(cse => cse.ToString()).Reverse());
         }
 
-        public override string ToString()
+        internal string BuildCallStackString(Location location)
         {
-            return string.Join("->", _stack.Select(cse => cse.ToString()).Reverse());
+            static void AppendLocation(
+                StringBuilder sb,
+                string shortDescription,
+                Location loc,
+                in NodeList<Expression> arguments)
+            {
+                sb
+                    .Append("    at ")
+                    .Append(shortDescription);
+
+                if (arguments.Count > 0)
+                {
+                    sb.Append(" (");
+                }
+
+                for (var index = 0; index < arguments.Count; index++)
+                {
+                    if (index != 0)
+                    {
+                        sb.Append(", ");
+                    }
+
+                    var arg = arguments[index];
+                    sb.Append(GetPropertyKey(arg));
+                }
+
+                if (arguments.Count > 0)
+                {
+                    sb.Append(") ");
+                }
+
+                sb
+                    .Append(loc.Source)
+                    .Append(":")
+                    .Append(loc.Start.Line)
+                    .Append(":")
+                    .Append(loc.Start.Column + 1) // report column number instead of index
+                    .AppendLine();
+            }
+
+            using var sb = StringBuilderPool.Rent();
+
+            // stack is one frame behind function-wise when we start to process it from expression level
+            var index = _stack._size - 1;
+            var element = index >= 0 ? _stack[index] : (CallStackElement?) null;
+            var shortDescription = element?.ToString() ?? "";
+            var arguments = element?.Function._functionDefinition?.Function.Params ?? new NodeList<Expression>();
+
+            AppendLocation(sb.Builder, shortDescription, location, arguments);
+
+            location = element?.Location ?? default;
+            index--;
+
+            while (index >= -1)
+            {
+                element = index >= 0 ? _stack[index] : null;
+                shortDescription = element?.ToString() ?? "";
+                arguments = element?.Function._functionDefinition?.Function.Params ?? new NodeList<Expression>();
+
+                AppendLocation(sb.Builder, shortDescription, location, arguments);
+
+                location = element?.Location ?? default;
+                index--;
+            }
+
+            return sb.ToString().TrimEnd();
         }
 
-        IEnumerator IEnumerable.GetEnumerator()
+        /// <summary>
+        /// A version of <see cref="EsprimaExtensions.GetKey"/> that cannot get into loop as we are already building a stack.
+        /// </summary>
+        private static string GetPropertyKey(Expression expression)
         {
-            return GetEnumerator();
+            if (expression is Literal literal)
+            {
+                return EsprimaExtensions.LiteralKeyToString(literal);
+            }
+
+            if (expression is Identifier identifier)
+            {
+                return identifier.Name ?? "";
+            }
+
+            if (expression is StaticMemberExpression staticMemberExpression)
+            {
+                return GetPropertyKey(staticMemberExpression.Object) + "." +
+                       GetPropertyKey(staticMemberExpression.Property);
+            }
+
+            return "?";
         }
     }
-}
+}

+ 2 - 0
Jint/Runtime/CommonProperties.cs

@@ -23,5 +23,7 @@ namespace Jint.Runtime
         internal static readonly JsString Writable = new JsString("writable");
         internal static readonly JsString Enumerable = new JsString("enumerable");
         internal static readonly JsString Configurable = new JsString("configurable");
+        internal static readonly JsString Stack = new JsString("stack");
+        internal static readonly JsString Message = new JsString("message");
     }
 }

+ 3 - 3
Jint/Runtime/Debugger/DebugHandler.cs

@@ -22,9 +22,9 @@ namespace Jint.Runtime.Debugger
             _steppingDepth = int.MaxValue;
         }
 
-        internal void AddToDebugCallStack(JsValue function, CallExpression callExpression)
+        internal void AddToDebugCallStack(JsValue function)
         {
-            string name = GetCalleeName(function, callExpression.Callee);
+            string name = GetCalleeName(function);
 
             _debugCallStack.Push(name);
         }
@@ -37,7 +37,7 @@ namespace Jint.Runtime.Debugger
             }
         }
 
-        private string GetCalleeName(JsValue function, Expression calleeExpression)
+        private string GetCalleeName(JsValue function)
         {
             switch (function)
             {

+ 1 - 1
Jint/Runtime/ExceptionHelper.cs

@@ -137,7 +137,7 @@ namespace Jint.Runtime
             throw new InvalidOperationException(message);
         }
 
-        public static void ThrowJavaScriptException(Engine engine, JsValue value, Completion result)
+        public static void ThrowJavaScriptException(Engine engine, JsValue value, in Completion result)
         {
             throw new JavaScriptException(value).SetCallstack(engine, result.Location);
         }

+ 1 - 1
Jint/Runtime/ExecutionCanceledException.cs

@@ -1,6 +1,6 @@
 namespace Jint.Runtime
 {
-    public class ExecutionCanceledException : JintException
+    public sealed class ExecutionCanceledException : JintException
     {
         public ExecutionCanceledException() : base("The script execution was canceled.")
         {

+ 41 - 0
Jint/Runtime/ExecutionContextStack.cs

@@ -0,0 +1,41 @@
+#nullable enable
+
+using System.Runtime.CompilerServices;
+using Jint.Collections;
+using Jint.Runtime.Environments;
+
+namespace Jint.Runtime
+{
+    internal sealed class ExecutionContextStack
+    {
+        private readonly RefStack<ExecutionContext> _stack;
+
+        public ExecutionContextStack(int capacity)
+        {
+            _stack = new RefStack<ExecutionContext>(capacity);
+        }
+
+        public void ReplaceTopLexicalEnvironment(LexicalEnvironment newEnv)
+        {
+            var array = _stack._array;
+            var size = _stack._size;
+            array[size - 1] = array[size - 1].UpdateLexicalEnvironment(newEnv);
+        }
+
+        public void ReplaceTopVariableEnvironment(LexicalEnvironment newEnv)
+        {
+            var array = _stack._array;
+            var size = _stack._size;
+            array[size - 1] = array[size - 1].UpdateVariableEnvironment(newEnv);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly ExecutionContext Peek() => ref _stack.Peek();
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Push(in ExecutionContext context) => _stack.Push(in context);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly ExecutionContext Pop() => ref _stack.Pop();
+    }
+}

+ 22 - 4
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -224,7 +224,24 @@ namespace Jint.Runtime.Interop
             return descriptor;
         }
 
-        private static ReflectionAccessor GetAccessor(Engine engine, Type type, string member)
+        // need to be public for advanced cases like RavenDB yielding properties from CLR objects
+        public static PropertyDescriptor GetPropertyDescriptor(Engine engine, object target, MemberInfo member)
+        {
+            // fast path which uses slow search if not found for some reason
+            ReflectionAccessor Factory()
+            {
+                return member switch
+                {
+                    PropertyInfo pi => new PropertyAccessor(pi.Name, pi),
+                    MethodBase mb => new MethodAccessor(MethodDescriptor.Build(new[] {mb})),
+                    FieldInfo fi => new FieldAccessor(fi),
+                    _ => null
+                };
+            }
+            return GetAccessor(engine, target.GetType(), member.Name, Factory).CreatePropertyDescriptor(engine, target);
+        }
+
+        private static ReflectionAccessor GetAccessor(Engine engine, Type type, string member, Func<ReflectionAccessor> accessorFactory = null)
         {
             var key = new ClrPropertyDescriptorFactoriesKey(type, member);
 
@@ -234,15 +251,16 @@ namespace Jint.Runtime.Interop
                 return accessor;
             }
 
-            var factory = ResolvePropertyDescriptorFactory(engine, type, member);
+            accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member);
+            
             // racy, we don't care, worst case we'll catch up later
             Interlocked.CompareExchange(ref Engine.ReflectionAccessors,
                 new Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor>(factories)
                 {
-                    [key] = factory
+                    [key] = accessor
                 }, factories);
 
-            return factory;
+            return accessor;
         }
 
         private static ReflectionAccessor ResolvePropertyDescriptorFactory(Engine engine, Type type, string memberName)

+ 1 - 35
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -8,9 +8,6 @@ namespace Jint.Runtime.Interpreter.Expressions
 {
     internal sealed class JintCallExpression : JintExpression
     {
-        private readonly bool _isDebugMode;
-        private readonly int _maxRecursionDepth;
-
         private CachedArgumentsHolder _cachedArguments;
         private bool _cached;
 
@@ -20,8 +17,6 @@ namespace Jint.Runtime.Interpreter.Expressions
         public JintCallExpression(Engine engine, CallExpression expression) : base(engine, expression)
         {
             _initialized = false;
-            _isDebugMode = engine.Options.IsDebugMode;
-            _maxRecursionDepth = engine.Options.MaxRecursionDepth;
             _calleeExpression = Build(engine, expression.Callee);
         }
 
@@ -100,23 +95,9 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
-
             var func = _engine.GetValue(callee, false);
             var r = callee as Reference;
 
-            if (_maxRecursionDepth >= 0)
-            {
-                var stackItem = new CallStackElement(expression, func, r?.GetReferencedName()?.ToString() ?? "anonymous function");
-
-                var recursionDepth = _engine.CallStack.Push(stackItem);
-
-                if (recursionDepth > _maxRecursionDepth)
-                {
-                    _engine.CallStack.Pop();
-                    ExceptionHelper.ThrowRecursionDepthOverflowException(_engine.CallStack, stackItem.ToString());
-                }
-            }
-
             if (func._type == InternalTypes.Undefined)
             {
                 ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'");
@@ -159,22 +140,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
-            if (_isDebugMode)
-            {
-                _engine.DebugHandler.AddToDebugCallStack(func, expression);
-            }
-
-            var result = callable.Call(thisObject, arguments);
-
-            if (_isDebugMode)
-            {
-                _engine.DebugHandler.PopDebugCallStack();
-            }
-
-            if (_maxRecursionDepth >= 0)
-            {
-                _engine.CallStack.Pop();
-            }
+            var result = _engine.Call(callable, thisObject, arguments, expression.Location);
 
             if (!_cached && arguments.Length > 0)
             {

+ 13 - 8
Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs

@@ -10,6 +10,8 @@ namespace Jint.Runtime.Interpreter.Expressions
     /// </summary>
     internal sealed class JintMemberExpression : JintExpression
     {
+        private MemberExpression _memberExpression;
+
         private JintExpression _objectExpression;
         private JintIdentifierExpression _objectIdentifierExpression;
         private JintThisExpression _objectThisExpression;
@@ -24,23 +26,23 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         protected override void Initialize()
         {
-            var expression = (MemberExpression) _expression;
-            _objectExpression = Build(_engine, expression.Object);
+            _memberExpression = (MemberExpression) _expression;
+            _objectExpression = Build(_engine, _memberExpression.Object);
             _objectIdentifierExpression = _objectExpression as JintIdentifierExpression;
             _objectThisExpression = _objectExpression as JintThisExpression;
 
-            if (!expression.Computed)
+            if (!_memberExpression.Computed)
             {
-                _determinedProperty = ((Identifier) expression.Property).Name;
+                _determinedProperty = ((Identifier) _memberExpression.Property).Name;
             }
-            else if (expression.Property.Type == Nodes.Literal)
+            else if (_memberExpression.Property.Type == Nodes.Literal)
             {
-                _determinedProperty = JintLiteralExpression.ConvertToJsValue((Literal) expression.Property);
+                _determinedProperty = JintLiteralExpression.ConvertToJsValue((Literal) _memberExpression.Property);
             }
 
             if (_determinedProperty is null)
             {
-                _propertyExpression = Build(_engine, expression.Property);
+                _propertyExpression = Build(_engine, _memberExpression.Property);
             }
         }
 
@@ -84,7 +86,10 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
 
             var property = _determinedProperty ?? _propertyExpression.GetValue();
-            TypeConverter.CheckObjectCoercible(_engine, baseValue, (MemberExpression) _expression, _determinedProperty?.ToString() ?? baseReferenceName);
+            if (baseValue.IsNullOrUndefined())
+            {
+                TypeConverter.CheckObjectCoercible(_engine, baseValue, _memberExpression.Property, _determinedProperty?.ToString() ?? baseReferenceName);
+            }
 
             // only convert if necessary
             var propertyKey = property.IsInteger() && baseValue.IsIntegerIndexedArray

+ 1 - 1
Jint/Runtime/Interpreter/JintStatementList.cs

@@ -6,7 +6,7 @@ using Jint.Runtime.Interpreter.Statements;
 
 namespace Jint.Runtime.Interpreter
 {
-    public class JintStatementList
+    internal class JintStatementList
     {
         private class Pair
         {

+ 72 - 95
Jint/Runtime/JavaScriptException.cs

@@ -1,156 +1,133 @@
-using System;
+#nullable enable
+
+using System;
 using Esprima;
-using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Error;
+using Jint.Native.Object;
 using Jint.Pooling;
 
 namespace Jint.Runtime
 {
     public class JavaScriptException : JintException
     {
-        private string _callStack;
+        private string? _callStack;
 
         public JavaScriptException(ErrorConstructor errorConstructor) : base("")
         {
             Error = errorConstructor.Construct(Arguments.Empty);
         }
 
-        public JavaScriptException(ErrorConstructor errorConstructor, string message, Exception innerException)
+        public JavaScriptException(ErrorConstructor errorConstructor, string? message, Exception? innerException)
              : base(message, innerException)
         {
             Error = errorConstructor.Construct(new JsValue[] { message });
         }
 
-        public JavaScriptException(ErrorConstructor errorConstructor, string message)
+        public JavaScriptException(ErrorConstructor errorConstructor, string? message)
             : base(message)
         {
             Error = errorConstructor.Construct(new JsValue[] { message });
         }
 
         public JavaScriptException(JsValue error)
-            : base(GetErrorMessage(error))
         {
             Error = error;
         }
 
-        public JavaScriptException SetCallstack(Engine engine, Location? location = null)
+        internal JavaScriptException SetCallstack(Engine engine, Location location)
         {
-            Location = location ?? default;
-
-            using (var sb = StringBuilderPool.Rent())
+            Location = location;
+            var value = engine.CallStack.BuildCallStackString(location);
+            _callStack = value;
+            if (Error.IsObject())
             {
-                foreach (var cse in engine.CallStack)
-                {
-                    sb.Builder.Append(" at ")
-                        .Append(cse)
-                        .Append("(");
-
-                    for (var index = 0; index < cse.CallExpression.Arguments.Count; index++)
-                    {
-                        if (index != 0)
-                        {
-                            sb.Builder.Append(", ");
-                        }
-                        var arg = cse.CallExpression.Arguments[index];
-                        if (arg is Expression pke)
-                        {
-                            sb.Builder.Append(GetPropertyKey(pke));
-                        }
-                        else
-                        {
-                            sb.Builder.Append(arg);
-                        }
-                    }
-
-                    sb.Builder.Append(") @ ")
-                        .Append(cse.CallExpression.Location.Source)
-                        .Append(" ")
-                        .Append(cse.CallExpression.Location.Start.Column)
-                        .Append(":")
-                        .Append(cse.CallExpression.Location.Start.Line)
-                        .AppendLine();
-                }
-                CallStack = sb.ToString();
+                Error.AsObject()
+                    .FastAddProperty(CommonProperties.Stack, new JsString(value), false, false, false);
             }
 
             return this;
         }
 
-        /// <summary>
-        /// A version of <see cref="EsprimaExtensions.GetKey"/> that cannot get into loop as we are already building a stack.
-        /// </summary>
-        private static string GetPropertyKey(Expression expression)
-        {
-            if (expression is Literal literal)
-            {
-                return EsprimaExtensions.LiteralKeyToString(literal);
-            }
-
-            if (expression is Identifier identifier)
-            {
-                return identifier.Name;
-            }
-
-            if (expression is StaticMemberExpression staticMemberExpression)
-            {
-                return GetPropertyKey(staticMemberExpression.Object) + "." + GetPropertyKey(staticMemberExpression.Property);
-            }
-
-            return "?";
-        }
-
-        private static string GetErrorMessage(JsValue error)
+        private string? GetErrorMessage()
         {
-            if (error.IsObject())
+            if (Error is ObjectInstance oi)
             {
-                var oi = error.AsObject();
-                var message = oi.Get("message", oi).ToString();
-                return message;
+                return oi.Get(CommonProperties.Message).ToString();
             }
-            if (error.IsString())
-                return error.ToString();
 
-            return error.ToString();
+            return null;
         }
 
         public JsValue Error { get; }
 
-        public override string ToString()
-        {
-            return Error.ToString();
-        }
+        public override string Message => GetErrorMessage() ?? TypeConverter.ToString(Error);
 
-        public string CallStack
+        /// <summary>
+        /// Returns the call stack of the exception. Requires that engine was built using
+        /// <see cref="Options.CollectStackTrace"/>.
+        /// </summary>
+        public override string? StackTrace
         {
             get
             {
-                if (_callStack != null)
+                if (_callStack is not null)
+                {
                     return _callStack;
-                if (ReferenceEquals(Error, null))
-                    return null;
-                if (Error.IsObject() == false)
-                    return null;
-                var callstack = Error.AsObject().Get("callstack", Error);
-                if (callstack.IsUndefined())
-                    return null;
-                return callstack.AsString();
-            }
-            set
-            {
-                _callStack = value;
-                if (value != null && Error.IsObject())
+                }
+
+                if (Error is not ObjectInstance oi)
                 {
-                    Error.AsObject()
-                        .FastAddProperty("callstack", new JsString(value), false, false, false);
+                    return null;
                 }
+
+                var callstack = oi.Get(CommonProperties.Stack, Error);
+
+                return callstack.IsUndefined()
+                    ? null 
+                    : callstack.AsString();
             }
         }
 
-        public Location Location { get; set; }
+        public Location Location { get; private set; }
 
         public int LineNumber => Location.Start.Line;
 
         public int Column => Location.Start.Column;
+
+        public override string ToString()
+        {
+            // adapted custom version as logic differs between full framework and .NET Core
+            var className = GetType().ToString();
+            var message = Message;
+            var innerExceptionString = InnerException?.ToString() ?? "";
+            const string endOfInnerExceptionResource = "--- End of inner exception stack trace ---";
+            var stackTrace = StackTrace;
+ 
+            using var rent = StringBuilderPool.Rent();
+            var sb = rent.Builder;
+            sb.Append(className);
+            if (!string.IsNullOrEmpty(message))
+            {
+                sb.Append(": ");
+                sb.Append(message);
+            }
+            if (InnerException != null)
+            {
+                sb.Append(Environment.NewLine);
+                sb.Append(" ---> ");
+                sb.Append(innerExceptionString);
+                sb.Append(Environment.NewLine);
+                sb.Append("   ");
+                sb.Append(endOfInnerExceptionResource);
+            }
+            if (stackTrace != null)
+            {
+                sb.Append(Environment.NewLine);
+                sb.Append(stackTrace);
+            }
+ 
+            return rent.ToString();
+        }
     }
 }

+ 1 - 5
Jint/Runtime/MemoryLimitExceededException.cs

@@ -1,11 +1,7 @@
 namespace Jint.Runtime
 {
-    public class MemoryLimitExceededException : JintException
+    public sealed class MemoryLimitExceededException : JintException
     {
-        public MemoryLimitExceededException() : base()
-        {
-        }
-
         public MemoryLimitExceededException(string message) : base(message)
         {
         }

+ 4 - 4
Jint/Runtime/RecursionDepthOverflowException.cs

@@ -2,13 +2,13 @@
 
 namespace Jint.Runtime
 {
-    public class RecursionDepthOverflowException : JintException
+    public sealed class RecursionDepthOverflowException : JintException
     {
-        public string CallChain { get; private set; }
+        public string CallChain { get; }
 
-        public string CallExpressionReference { get; private set; }
+        public string CallExpressionReference { get; }
 
-        public RecursionDepthOverflowException(JintCallStack currentStack, string currentExpressionReference)
+        internal RecursionDepthOverflowException(JintCallStack currentStack, string currentExpressionReference)
             : base("The recursion is forbidden by script host.")
         {
             CallExpressionReference = currentExpressionReference;

+ 0 - 97
Jint/Runtime/RefStack.cs

@@ -1,97 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using Jint.Runtime.Environments;
-
-namespace Jint.Runtime
-{
-    internal sealed class ExecutionContextStack
-    {
-        private ExecutionContext[] _array;
-        private int _size;
-
-        private const int DefaultCapacity = 2;
-
-        public ExecutionContextStack(int capacity)
-        {
-            _array = new ExecutionContext[capacity];
-            _size = 0;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public ref readonly ExecutionContext Peek()
-        {
-            if (_size == 0)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
-            }
-            return ref _array[_size - 1];
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void Pop()
-        {
-            if (_size == 0)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
-            }
-            _size--;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void Push(in ExecutionContext item)
-        {
-            if (_size == _array.Length)
-            {
-                EnsureCapacity(_size + 1);
-            }
-            _array[_size++] = item;
-        }
-
-        private void EnsureCapacity(int min)
-        {
-            if (_array.Length < min)
-            {
-                int newCapacity = _array.Length == 0
-                    ? DefaultCapacity
-                    : _array.Length * 2;
-
-                if (newCapacity < min)
-                {
-                    newCapacity = min;
-                }
-                Resize(newCapacity);
-            }
-        }
-
-        private void Resize(int value)
-        {
-            if (value != _array.Length)
-            {
-                if (value > 0)
-                {
-                    var newItems = new ExecutionContext[value];
-                    if (_size > 0)
-                    {
-                        Array.Copy(_array, 0, newItems, 0, _size);
-                    }
-
-                    _array = newItems;
-                }
-                else
-                {
-                    _array = Array.Empty<ExecutionContext>();
-                }
-            }
-        }
-
-        public void ReplaceTopLexicalEnvironment(LexicalEnvironment newEnv)
-        {
-            _array[_size - 1] = _array[_size - 1].UpdateLexicalEnvironment(newEnv);
-        }
-
-        public void ReplaceTopVariableEnvironment(LexicalEnvironment newEnv)
-        {
-            _array[_size - 1] = _array[_size - 1].UpdateVariableEnvironment(newEnv);
-        }
-    }
-}

+ 1 - 1
Jint/Runtime/StatementsCountOverflowException.cs

@@ -1,6 +1,6 @@
 namespace Jint.Runtime
 {
-    public class StatementsCountOverflowException : JintException 
+    public sealed class StatementsCountOverflowException : JintException 
     {
         public StatementsCountOverflowException() : base("The maximum number of statements executed have been reached.")
         {

+ 9 - 6
Jint/Runtime/TypeConverter.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Runtime.CompilerServices;
+using Esprima;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Number;
@@ -510,27 +511,29 @@ namespace Jint.Runtime
             };
         }
         
+        [MethodImpl(MethodImplOptions.NoInlining)]
         internal static void CheckObjectCoercible(
             Engine engine,
             JsValue o,
-            MemberExpression expression,
+            Node sourceNode,
             string referenceName)
         {
-            if (o._type < InternalTypes.Boolean && !engine.Options.ReferenceResolver.CheckCoercible(o))
+            if (!engine.Options.ReferenceResolver.CheckCoercible(o))
             {
-                ThrowTypeError(engine, o, expression, referenceName);
+                ThrowMemberNullOrUndefinedError(engine, o, sourceNode.Location, referenceName);
             }
         }
 
-        private static void ThrowTypeError(
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void ThrowMemberNullOrUndefinedError(
             Engine engine,
             JsValue o,
-            MemberExpression expression,
+            in Location location,
             string referencedName)
         {
             referencedName ??= "unknown";
             var message = $"Cannot read property '{referencedName}' of {o}";
-            throw new JavaScriptException(engine.TypeError, message).SetCallstack(engine, expression.Location);
+            throw new JavaScriptException(engine.TypeError, message).SetCallstack(engine, location);
         }
 
         public static void CheckObjectCoercible(Engine engine, JsValue o)