Browse Source

Return storage of last syntax node to engine instance (#1437)

Marko Lahma 2 years ago
parent
commit
cf3714b0ee

+ 10 - 4
Jint.Tests.PublicInterface/ConstraintUsageTests.cs

@@ -3,6 +3,7 @@ using Jint.Runtime;
 
 
 namespace Jint.Tests.PublicInterface;
 namespace Jint.Tests.PublicInterface;
 
 
+[Collection("ConstraintUsageTests")]
 public class ConstraintUsageTests
 public class ConstraintUsageTests
 {
 {
     [Fact]
     [Fact]
@@ -27,13 +28,18 @@ public class ConstraintUsageTests
         {
         {
             var result = engine.Evaluate(@"
             var result = engine.Evaluate(@"
                 function sleep(millisecondsTimeout) {
                 function sleep(millisecondsTimeout) {
-                    var x = 0;
                     var totalMilliseconds = new Date().getTime() + millisecondsTimeout;
                     var totalMilliseconds = new Date().getTime() + millisecondsTimeout;
-                    while (new Date().getTime() < totalMilliseconds) {
-                        x++;
+                    var now = new Date().getTime();
+                    while (now < totalMilliseconds) {
+                        // simulate some work
+                        now = new Date().getTime();
+                        now = new Date().getTime();
+                        now = new Date().getTime();
+                        now = new Date().getTime();
+                        now = new Date().getTime();
                     }
                     }
                 }
                 }
-                sleep(200);
+                sleep(300);
                 return 'done';
                 return 'done';
             ");
             ");
             return result.AsString();
             return result.AsString();

+ 47 - 48
Jint.Tests.PublicInterface/PublicInterfaceTests.cs

@@ -2,23 +2,22 @@ using System.Collections.Concurrent;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Function;
 
 
-namespace Jint.Tests.PublicInterface
+namespace Jint.Tests.PublicInterface;
+
+public class PublicInterfaceTests
 {
 {
-    public class PublicInterfaceTests
+    [Fact]
+    public void BindFunctionInstancesArePublic()
     {
     {
-
-        [Fact]
-        public void BindFunctionInstancesArePublic()
+        var engine = new Engine(options =>
         {
         {
-            var engine = new Engine(options =>
-            {
-                options.AllowClr();
-            });
+            options.AllowClr();
+        });
 
 
-            using var emulator = new SetTimeoutEmulator(engine);
+        using var emulator = new SetTimeoutEmulator(engine);
 
 
-            engine.SetValue("emulator", emulator);
-            engine.Execute(@"
+        engine.SetValue("emulator", emulator);
+        engine.Execute(@"
 var coolingObject = {
 var coolingObject = {
     coolDownTime: 1000,
     coolDownTime: 1000,
     cooledDown: false
     cooledDown: false
@@ -29,27 +28,27 @@ var coolingObject = {
     }.bind(coolingObject), coolingObject.coolDownTime);
     }.bind(coolingObject), coolingObject.coolDownTime);
 
 
 ");
 ");
+    }
 
 
-        }
+    private sealed class SetTimeoutEmulator : IDisposable
+    {
+        private readonly Engine _engine;
+        private readonly ConcurrentQueue<JsValue> _queue = new();
+        private readonly Task _queueProcessor;
+        private readonly CancellationTokenSource _quit = new();
+        private bool _disposedValue;
 
 
-        private class SetTimeoutEmulator : IDisposable
+        public SetTimeoutEmulator(Engine engine)
         {
         {
-            private readonly Engine _engine;
-            private readonly ConcurrentQueue<JsValue> _queue = new();
-            private readonly Task _queueProcessor;
-            private readonly CancellationTokenSource _quit = new();
-            private bool _disposedValue;
+            _engine = engine ?? throw new ArgumentNullException(nameof(engine));
 
 
-            public SetTimeoutEmulator(Engine engine)
+            _queueProcessor = Task.Run(() =>
             {
             {
-                _engine = engine ?? throw new ArgumentNullException(nameof(engine));
-
-                _queueProcessor = Task.Run(() =>
+                while (!_quit.IsCancellationRequested)
                 {
                 {
-
-                    while (!_quit.IsCancellationRequested)
+                    while (_queue.TryDequeue(out var queueEntry))
                     {
                     {
-                        while (_queue.TryDequeue(out var queueEntry))
+                        lock (_engine)
                         {
                         {
                             if (queueEntry is FunctionInstance fi)
                             if (queueEntry is FunctionInstance fi)
                             {
                             {
@@ -65,35 +64,35 @@ var coolingObject = {
                             }
                             }
                         }
                         }
                     }
                     }
-                });
-            }
+                }
+            });
+        }
 
 
-            public void SetTimeout(JsValue script, object timeout)
-            {
-                _queue.Enqueue(script);
-            }
+        public void SetTimeout(JsValue script, object timeout)
+        {
+            _queue.Enqueue(script);
+        }
 
 
-            protected virtual void Dispose(bool disposing)
+        protected void Dispose(bool disposing)
+        {
+            if (!_disposedValue)
             {
             {
-                if (!_disposedValue)
+                if (disposing)
                 {
                 {
-                    if (disposing)
-                    {
-                        // TODO: dispose managed state (managed objects)
-                        _quit.Cancel();
-                        _queueProcessor.Wait();
-                    }
-
-                    _disposedValue = true;
+                    // TODO: dispose managed state (managed objects)
+                    _quit.Cancel();
+                    _queueProcessor.Wait();
                 }
                 }
-            }
 
 
-            public void Dispose()
-            {
-                // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-                Dispose(disposing: true);
-                GC.SuppressFinalize(this);
+                _disposedValue = true;
             }
             }
         }
         }
+
+        public void Dispose()
+        {
+            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
     }
     }
 }
 }

+ 377 - 402
Jint.Tests/Runtime/PromiseTests.cs

@@ -1,526 +1,501 @@
 using Jint.Native;
 using Jint.Native;
+using Jint.Native.Object;
 using Jint.Native.Promise;
 using Jint.Native.Promise;
 using Jint.Runtime;
 using Jint.Runtime;
 
 
 // obsolete GetCompletionValue
 // obsolete GetCompletionValue
 #pragma warning disable 618
 #pragma warning disable 618
 
 
-namespace Jint.Tests.Runtime
+namespace Jint.Tests.Runtime;
+
+public class PromiseTests
 {
 {
-    public class PromiseTests
+    [Fact]
+    public void RegisterPromise_CalledWithinExecute_ResolvesCorrectly()
     {
     {
-        #region Manual Promise
-
-        [Fact]
-        public void RegisterPromise_CalledWithinExecute_ResolvesCorrectly()
-        {
-            Action<JsValue> resolveFunc = null;
-
-            var engine = new Engine();
-            engine.SetValue("f", new Func<JsValue>(() =>
-            {
-                var (promise, resolve, _) = engine.RegisterPromise();
-                resolveFunc = resolve;
-                return promise;
-            }));
-
-            var promise = engine.Evaluate("f();");
+        Action<JsValue> resolveFunc = null;
 
 
-            resolveFunc(66);
-            Assert.Equal(66, promise.UnwrapIfPromise());
-        }
-
-        [Fact]
-        public void RegisterPromise_CalledWithinExecute_RejectsCorrectly()
+        var engine = new Engine();
+        engine.SetValue("f", new Func<JsValue>(() =>
         {
         {
-            Action<JsValue> rejectFunc = null;
-
-            var engine = new Engine();
-            engine.SetValue("f", new Func<JsValue>(() =>
-            {
-                var (promise, _, reject) = engine.RegisterPromise();
-                rejectFunc = reject;
-                return promise;
-            }));
-
-            engine.Execute("f();");
+            var (promise, resolve, _) = engine.RegisterPromise();
+            resolveFunc = resolve;
+            return promise;
+        }));
 
 
-            var completion = engine.Evaluate("f();");
+        var promise = engine.Evaluate("f();");
 
 
-            rejectFunc("oops!");
-
-            var ex = Assert.Throws<PromiseRejectedException>(() => { completion.UnwrapIfPromise(); });
+        resolveFunc(66);
+        Assert.Equal(66, promise.UnwrapIfPromise());
+    }
 
 
-            Assert.Equal("oops!", ex.RejectedValue.AsString());
-        }
+    [Fact]
+    public void RegisterPromise_CalledWithinExecute_RejectsCorrectly()
+    {
+        Action<JsValue> rejectFunc = null;
 
 
-        [Fact]
-        public void RegisterPromise_UsedWithRace_WorksFlawlessly()
+        var engine = new Engine();
+        engine.SetValue("f", new Func<JsValue>(() =>
         {
         {
-            var engine = new Engine();
-
-            Action<JsValue> resolve1 = null;
-            engine.SetValue("f1", new Func<JsValue>(() =>
-            {
-                var (promise, resolve, _) = engine.RegisterPromise();
-                resolve1 = resolve;
-                return promise;
-            }));
-
-            Action<JsValue> resolve2 = null;
-            engine.SetValue("f2", new Func<JsValue>(() =>
-            {
-                var (promise, resolve, _) = engine.RegisterPromise();
-                resolve2 = resolve;
-                return promise;
-            }));
-
-            var completion = engine.Evaluate("Promise.race([f1(), f2()]);");
-
-            resolve1("first");
+            var (promise, _, reject) = engine.RegisterPromise();
+            rejectFunc = reject;
+            return promise;
+        }));
 
 
-            // still not finished but the promise is fulfilled
-            Assert.Equal("first", completion.UnwrapIfPromise());
+        engine.Execute("f();");
 
 
-            resolve2("second");
+        var completion = engine.Evaluate("f();");
 
 
-            // completion value hasn't changed
-            Assert.Equal("first", completion.UnwrapIfPromise());
-        }
-
-        #endregion
-
-        #region Execute
-
-        [Fact]
-        public void Execute_ConcurrentNormalExecuteCall_WorksFine()
-        {
-            var engine = new Engine();
-            engine.SetValue("f", new Func<JsValue>(() => engine.RegisterPromise().Promise));
+        rejectFunc("oops!");
 
 
-            engine.Execute("f();");
+        var ex = Assert.Throws<PromiseRejectedException>(() => { completion.UnwrapIfPromise(); });
 
 
-            Assert.Equal(true, engine.Evaluate(" 1 + 1 === 2"));
-        }
-
-        #endregion
+        Assert.Equal("oops!", ex.RejectedValue.AsString());
+    }
 
 
-        #region Ctor
+    [Fact]
+    public void RegisterPromise_UsedWithRace_WorksFlawlessly()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseCtorWithNoResolver_Throws()
+        Action<JsValue> resolve1 = null;
+        engine.SetValue("f1", new Func<JsValue>(() =>
         {
         {
-            var engine = new Engine();
+            var (promise, resolve, _) = engine.RegisterPromise();
+            resolve1 = resolve;
+            return promise;
+        }));
 
 
-            Assert.Throws<JavaScriptException>(() => { engine.Execute("new Promise();"); });
-        }
-
-        [Fact(Timeout = 5000)]
-        public void PromiseCtorWithInvalidResolver_Throws()
+        Action<JsValue> resolve2 = null;
+        engine.SetValue("f2", new Func<JsValue>(() =>
         {
         {
-            var engine = new Engine();
+            var (promise, resolve, _) = engine.RegisterPromise();
+            resolve2 = resolve;
+            return promise;
+        }));
 
 
-            Assert.Throws<JavaScriptException>(() => { engine.Execute("new Promise({});"); });
-        }
+        var completion = engine.Evaluate("Promise.race([f1(), f2()]);");
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseCtorWithValidResolver_DoesNotThrow()
-        {
-            var engine = new Engine();
+        resolve1("first");
 
 
-            engine.Execute("new Promise((resolve, reject)=>{});");
-        }
+        // still not finished but the promise is fulfilled
+        Assert.Equal("first", completion.UnwrapIfPromise());
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseCtor_ReturnsPromiseJsValue()
-        {
-            var engine = new Engine();
-            var promise = engine.Evaluate("new Promise((resolve, reject)=>{});");
+        resolve2("second");
 
 
-            Assert.IsType<PromiseInstance>(promise);
-        }
+        // completion value hasn't changed
+        Assert.Equal("first", completion.UnwrapIfPromise());
+    }
 
 
-        #endregion
+    [Fact]
+    public void Execute_ConcurrentNormalExecuteCall_WorksFine()
+    {
+        var engine = new Engine();
+        engine.SetValue("f", new Func<JsValue>(() => engine.RegisterPromise().Promise));
 
 
-        #region Resolve
+        engine.Execute("f();");
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseResolveViaResolver_ReturnsCorrectValue()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate("new Promise((resolve, reject)=>{resolve(66);});").UnwrapIfPromise();
-            Assert.Equal(66, res);
-        }
+        Assert.Equal(true, engine.Evaluate(" 1 + 1 === 2"));
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseResolveViaStatic_ReturnsCorrectValue()
-        {
-            var engine = new Engine();
-            Assert.Equal(66, engine.Evaluate("Promise.resolve(66);").UnwrapIfPromise());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseCtorWithNoResolver_Throws()
+    {
+        var engine = new Engine();
 
 
-        #endregion
+        Assert.Throws<JavaScriptException>(() => { engine.Execute("new Promise();"); });
+    }
 
 
-        #region Reject
+    [Fact(Timeout = 5000)]
+    public void PromiseCtorWithInvalidResolver_Throws()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRejectViaResolver_ThrowsPromiseRejectedException()
-        {
-            var engine = new Engine();
+        Assert.Throws<JavaScriptException>(() => { engine.Execute("new Promise({});"); });
+    }
 
 
-            var ex = Assert.Throws<PromiseRejectedException>(() =>
-            {
-                engine.Evaluate("new Promise((resolve, reject)=>{reject('Could not connect');});").UnwrapIfPromise();
-            });
+    [Fact(Timeout = 5000)]
+    public void PromiseCtorWithValidResolver_DoesNotThrow()
+    {
+        var engine = new Engine();
 
 
-            Assert.Equal("Could not connect", ex.RejectedValue.AsString());
-        }
+        engine.Execute("new Promise((resolve, reject)=>{});");
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRejectViaStatic_ThrowsPromiseRejectedException()
-        {
-            var engine = new Engine();
+    [Fact(Timeout = 5000)]
+    public void PromiseCtor_ReturnsPromiseJsValue()
+    {
+        var engine = new Engine();
+        var promise = engine.Evaluate("new Promise((resolve, reject)=>{});");
 
 
-            var ex = Assert.Throws<PromiseRejectedException>(() =>
-            {
-                engine.Evaluate("Promise.reject('Could not connect');").UnwrapIfPromise();
-            });
+        Assert.IsType<PromiseInstance>(promise);
+    }
 
 
-            Assert.Equal("Could not connect", ex.RejectedValue.AsString());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseResolveViaResolver_ReturnsCorrectValue()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate("new Promise((resolve, reject)=>{resolve(66);});").UnwrapIfPromise();
+        Assert.Equal(66, res);
+    }
 
 
-        #endregion
+    [Fact(Timeout = 5000)]
+    public void PromiseResolveViaStatic_ReturnsCorrectValue()
+    {
+        var engine = new Engine();
+        Assert.Equal(66, engine.Evaluate("Promise.resolve(66);").UnwrapIfPromise());
+    }
 
 
-        #region Then
+    [Fact(Timeout = 5000)]
+    public void PromiseRejectViaResolver_ThrowsPromiseRejectedException()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThen_HandlerCalledWithCorrectValue()
+        var ex = Assert.Throws<PromiseRejectedException>(() =>
         {
         {
-            var engine = new Engine();
+            engine.Evaluate("new Promise((resolve, reject)=>{reject('Could not connect');});").UnwrapIfPromise();
+        });
 
 
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => 44).then(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal("Could not connect", ex.RejectedValue.AsString());
+    }
 
 
-            Assert.Equal(44, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRejectViaStatic_ThrowsPromiseRejectedException()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseThen_ReturnsNewPromiseInstance()
+        var ex = Assert.Throws<PromiseRejectedException>(() =>
         {
         {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "var promise1 = new Promise((resolve, reject) => { resolve(1); }); var promise2 = promise1.then();  promise1 === promise2").UnwrapIfPromise();
+            engine.Evaluate("Promise.reject('Could not connect');").UnwrapIfPromise();
+        });
 
 
-            Assert.Equal(false, res);
-        }
+        Assert.Equal("Could not connect", ex.RejectedValue.AsString());
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseThen_CalledCorrectlyOnResolve()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(result => resolve(result)); });").UnwrapIfPromise();
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThen_HandlerCalledWithCorrectValue()
+    {
+        var engine = new Engine();
 
 
-            Assert.Equal(66, res);
-        }
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => 44).then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseResolveChainedWithHandler_ResolvedAsUndefined()
-        {
-            var engine = new Engine();
+        Assert.Equal(44, res);
+    }
 
 
-            Assert.Equal(JsValue.Undefined, engine.Evaluate("Promise.resolve(33).then(() => {});").UnwrapIfPromise());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseThen_ReturnsNewPromiseInstance()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "var promise1 = new Promise((resolve, reject) => { resolve(1); }); var promise2 = promise1.then();  promise1 === promise2").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThenWithUndefinedCallback_PassesThroughValueCorrectly()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then().then(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(false, res);
+    }
 
 
-            Assert.Equal(66, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseThen_CalledCorrectlyOnResolve()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThenWithCallbackReturningUndefined_PassesThroughUndefinedCorrectly()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => {}).then(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(66, res);
+    }
 
 
-            Assert.Equal(JsValue.Undefined, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseResolveChainedWithHandler_ResolvedAsUndefined()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThenThrowsError_ChainedCallsCatchWithThrownError()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => { throw 'Thrown Error'; }).catch(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(JsValue.Undefined, engine.Evaluate("Promise.resolve(33).then(() => {});").UnwrapIfPromise());
+    }
 
 
-            Assert.Equal("Thrown Error", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThenWithUndefinedCallback_PassesThroughValueCorrectly()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then().then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThenReturnsResolvedPromise_ChainedCallsThenWithPromiseValue()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => Promise.resolve(55)).then(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(66, res);
+    }
 
 
-            Assert.Equal(55, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThenWithCallbackReturningUndefined_PassesThroughUndefinedCorrectly()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => {}).then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedThenReturnsRejectedPromise_ChainedCallsCatchWithPromiseValue()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => Promise.reject('Error Message')).catch(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(JsValue.Undefined, res);
+    }
 
 
-            Assert.Equal("Error Message", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThenThrowsError_ChainedCallsCatchWithThrownError()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => { throw 'Thrown Error'; }).catch(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        #endregion
+        Assert.Equal("Thrown Error", res);
+    }
 
 
-        #region Catch
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThenReturnsResolvedPromise_ChainedCallsThenWithPromiseValue()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => Promise.resolve(55)).then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseCatch_CalledCorrectlyOnReject()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(55, res);
+    }
 
 
-            Assert.Equal("Could not connect", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedThenReturnsRejectedPromise_ChainedCallsCatchWithPromiseValue()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).then(() => Promise.reject('Error Message')).catch(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseThenWithCatch_CalledCorrectlyOnReject()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).then(undefined, result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal("Error Message", res);
+    }
 
 
-            Assert.Equal("Could not connect", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseCatch_CalledCorrectlyOnReject()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedWithHandler_ResolvedAsUndefined()
-        {
-            var engine = new Engine();
-            Assert.Equal(JsValue.Undefined, engine.Evaluate("Promise.reject('error').catch(() => {});").UnwrapIfPromise());
-        }
+        Assert.Equal("Could not connect", res);
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedCatchThen_ThenCallWithUndefined()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch(ex => {}).then(result => resolve(result)); });").UnwrapIfPromise();
+    [Fact(Timeout = 5000)]
+    public void PromiseThenWithCatch_CalledCorrectlyOnReject()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).then(undefined, result => resolve(result)); });").UnwrapIfPromise();
 
 
-            Assert.Equal(JsValue.Undefined, res);
-        }
+        Assert.Equal("Could not connect", res);
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedCatchWithUndefinedHandler_CatchChainedCorrectly()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch().catch(result => resolve(result)); });").UnwrapIfPromise();
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedWithHandler_ResolvedAsUndefined()
+    {
+        var engine = new Engine();
+        Assert.Equal(JsValue.Undefined, engine.Evaluate("Promise.reject('error').catch(() => {});").UnwrapIfPromise());
+    }
 
 
-            Assert.Equal("Could not connect", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedCatchThen_ThenCallWithUndefined()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch(ex => {}).then(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        #endregion
+        Assert.Equal(JsValue.Undefined, res);
+    }
 
 
-        #region Finally
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedCatchWithUndefinedHandler_CatchChainedCorrectly()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerReject('Could not connect')}).catch().catch(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseChainedFinally_HandlerCalled()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).finally(() => resolve(16)); });").UnwrapIfPromise();
+        Assert.Equal("Could not connect", res);
+    }
 
 
-            Assert.Equal(16, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseChainedFinally_HandlerCalled()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(66)}).finally(() => resolve(16)); });").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseFinally_ReturnsNewPromiseInstance()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "var promise1 = new Promise((resolve, reject) => { resolve(1); }); var promise2 = promise1.finally();  promise1 === promise2");
+        Assert.Equal(16, res);
+    }
 
 
-            Assert.Equal(false, res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseFinally_ReturnsNewPromiseInstance()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "var promise1 = new Promise((resolve, reject) => { resolve(1); }); var promise2 = promise1.finally();  promise1 === promise2");
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseFinally_ResolvesWithCorrectValue()
-        {
-            var engine = new Engine();
-            Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally(() => {})").UnwrapIfPromise());
-        }
+        Assert.Equal(false, res);
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseFinallyWithNoCallback_ResolvesWithCorrectValue()
-        {
-            var engine = new Engine();
-            Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally()").UnwrapIfPromise());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseFinally_ResolvesWithCorrectValue()
+    {
+        var engine = new Engine();
+        Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally(() => {})").UnwrapIfPromise());
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseFinallyChained_ResolvesWithCorrectValue()
-        {
-            var engine = new Engine();
+    [Fact(Timeout = 5000)]
+    public void PromiseFinallyWithNoCallback_ResolvesWithCorrectValue()
+    {
+        var engine = new Engine();
+        Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally()").UnwrapIfPromise());
+    }
 
 
-            Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally(() => 6).finally(() => 9);").UnwrapIfPromise());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseFinallyChained_ResolvesWithCorrectValue()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseFinallyWhichThrows_ResolvesWithError()
-        {
-            var engine = new Engine();
-            var res = engine.Evaluate(
-                "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(5)}).finally(() => {throw 'Could not connect';}).catch(result => resolve(result)); });").UnwrapIfPromise();
+        Assert.Equal(2, engine.Evaluate("Promise.resolve(2).finally(() => 6).finally(() => 9);").UnwrapIfPromise());
+    }
 
 
-            Assert.Equal("Could not connect", res);
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseFinallyWhichThrows_ResolvesWithError()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate(
+            "new Promise((resolve, reject) => { new Promise((innerResolve, innerReject) => {innerResolve(5)}).finally(() => {throw 'Could not connect';}).catch(result => resolve(result)); });").UnwrapIfPromise();
 
 
-        #endregion
+        Assert.Equal("Could not connect", res);
+    }
 
 
-        #region All
+    [Fact(Timeout = 5000)]
+    public void PromiseAll_BadIterable_Rejects()
+    {
+        var engine = new Engine();
+        Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.all();").UnwrapIfPromise(); });
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseAll_BadIterable_Rejects()
-        {
-            var engine = new Engine();
-            Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.all();").UnwrapIfPromise(); });
-        }
 
 
+    [Fact(Timeout = 5000)]
+    public void PromiseAll_ArgsAreNotPromises_ResolvesCorrectly()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseAll_ArgsAreNotPromises_ResolvesCorrectly()
-        {
-            var engine = new Engine();
+        Assert.Equal(new object[] {1d, 2d, 3d}, engine.Evaluate("Promise.all([1,2,3]);").UnwrapIfPromise().ToObject());
+    }
 
 
-            Assert.Equal(new object[] {1d, 2d, 3d}, engine.Evaluate("Promise.all([1,2,3]);").UnwrapIfPromise().ToObject());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseAll_MixturePromisesNoPromises_ResolvesCorrectly()
+    {
+        var engine = new Engine();
+        Assert.Equal(new object[] {1d, 2d, 3d},
+            engine.Evaluate("Promise.all([1,Promise.resolve(2),3]);").UnwrapIfPromise().ToObject());
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseAll_MixturePromisesNoPromises_ResolvesCorrectly()
-        {
-            var engine = new Engine();
-            Assert.Equal(new object[] {1d, 2d, 3d},
-                engine.Evaluate("Promise.all([1,Promise.resolve(2),3]);").UnwrapIfPromise().ToObject());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseAll_MixturePromisesNoPromisesOneRejects_ResolvesCorrectly()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseAll_MixturePromisesNoPromisesOneRejects_ResolvesCorrectly()
+        Assert.Throws<PromiseRejectedException>(() =>
         {
         {
-            var engine = new Engine();
+            engine.Evaluate("Promise.all([1,Promise.resolve(2),3, Promise.reject('Cannot connect')]);").UnwrapIfPromise();
+        });
+    }
 
 
-            Assert.Throws<PromiseRejectedException>(() =>
-            {
-                engine.Evaluate("Promise.all([1,Promise.resolve(2),3, Promise.reject('Cannot connect')]);").UnwrapIfPromise();
-            });
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRace_NoArgs_Rejects()
+    {
+        var engine = new Engine();
 
 
-        #endregion
+        Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.race();").UnwrapIfPromise(); });
+    }
 
 
-        #region Race
+    [Fact(Timeout = 5000)]
+    public void PromiseRace_InvalidIterator_Rejects()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRace_NoArgs_Rejects()
-        {
-            var engine = new Engine();
+        Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.race({});").UnwrapIfPromise(); });
+    }
 
 
-            Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.race();").UnwrapIfPromise(); });
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRaceNoPromises_ResolvesCorrectly()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRace_InvalidIterator_Rejects()
-        {
-            var engine = new Engine();
+        Assert.Equal(12d, engine.Evaluate("Promise.race([12,2,3]);").UnwrapIfPromise().ToObject());
+    }
 
 
-            Assert.Throws<PromiseRejectedException>(() => { engine.Evaluate("Promise.race({});").UnwrapIfPromise(); });
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRaceNoPromises_ResolvesCorrectly()
-        {
-            var engine = new Engine();
+        Assert.Equal(12d, engine.Evaluate("Promise.race([12,Promise.resolve(2),3]);").UnwrapIfPromise().ToObject());
+    }
 
 
-            Assert.Equal(12d, engine.Evaluate("Promise.race([12,2,3]);").UnwrapIfPromise().ToObject());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly2()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly()
-        {
-            var engine = new Engine();
+        Assert.Equal(2d, engine.Evaluate("Promise.race([Promise.resolve(2),6,3]);").UnwrapIfPromise().ToObject());
+    }
 
 
-            Assert.Equal(12d, engine.Evaluate("Promise.race([12,Promise.resolve(2),3]);").UnwrapIfPromise().ToObject());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly3()
+    {
+        var engine = new Engine();
+        var res = engine.Evaluate("Promise.race([new Promise((resolve,reject)=>{}),Promise.resolve(55),3]);").UnwrapIfPromise();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly2()
-        {
-            var engine = new Engine();
+        Assert.Equal(55d, res.ToObject());
+    }
 
 
-            Assert.Equal(2d, engine.Evaluate("Promise.race([Promise.resolve(2),6,3]);").UnwrapIfPromise().ToObject());
-        }
+    [Fact(Timeout = 5000)]
+    public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly4()
+    {
+        var engine = new Engine();
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly3()
+        Assert.Throws<PromiseRejectedException>(() =>
         {
         {
-            var engine = new Engine();
-            var res = engine.Evaluate("Promise.race([new Promise((resolve,reject)=>{}),Promise.resolve(55),3]);").UnwrapIfPromise();
-
-            Assert.Equal(55d, res.ToObject());
-        }
+            engine.Evaluate(
+                "Promise.race([new Promise((resolve,reject)=>{}),Promise.reject('Could not connect'),3]);").UnwrapIfPromise();
+        });
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRaceMixturePromisesNoPromises_ResolvesCorrectly4()
+    [Fact(Timeout = 5000)]
+    public void PromiseRegression_SingleElementArrayWithClrDictionaryInPromiseAll()
+    {
+        var engine = new Engine();
+        var dictionary = new Dictionary<string, object>
         {
         {
-            var engine = new Engine();
+            { "Value 1", 1 },
+            { "Value 2", "a string" }
+        };
+        engine.SetValue("clrDictionary", dictionary);
 
 
-            Assert.Throws<PromiseRejectedException>(() =>
-            {
-                engine.Evaluate(
-                    "Promise.race([new Promise((resolve,reject)=>{}),Promise.reject('Could not connect'),3]);").UnwrapIfPromise();
-            });
-        }
+        var resultAsObject = engine
+            .Evaluate(@"
+const promiseArray = [clrDictionary];
+return Promise.all(promiseArray);") // Returning and array through Promise.any()
+            .UnwrapIfPromise()
+            .ToObject();
 
 
-        #endregion
+        var result = (object[]) resultAsObject;
 
 
-        #region Regression
+        Assert.Single(result);
+        Assert.IsType<Dictionary<string, object>>(result[0]);
+    }
 
 
-        [Fact(Timeout = 5000)]
-        public void PromiseRegression_SingleElementArrayWithClrDictionaryInPromiseAll()
-        {
-            var engine = new Engine();
-            var dictionary = new Dictionary<string, object>
-            {
-                { "Value 1", 1 },
-                { "Value 2", "a string" }
-            };
-            engine.SetValue("clrDictionary", dictionary);
-
-            var resultAsObject = engine
-                .Evaluate(@"
-const promiseArray = [clrDictionary];
-return Promise.all(promiseArray);") // Returning and array through Promise.any()
-                .UnwrapIfPromise()
-                .ToObject();
+    [Fact]
+    public void ManualPromise_HasCorrectStackTrace()
+    {
+        using var engine = new Engine();
 
 
-            var result = (object[]) resultAsObject;
+        string logMessage = null;
+        var promise = engine.RegisterPromise();
+        engine.SetValue("log", new Action<JsValue>((error) => {
+            logMessage = (error as ObjectInstance)["stack"].ToString();
+        }));
+        engine.SetValue("getPromise", new Func<JsValue>(() => promise.Promise));
+        engine.Execute( "const thePromise = getPromise(); thePromise.then(() => new Error()).then(e => log(e));" );
 
 
-            Assert.Single(result);
-            Assert.IsType<Dictionary<string, object>>(result[0]);
-        }
+        // Calling this method will execute the JavaScript again.
+        promise.Resolve(JsValue.Undefined);
 
 
-        #endregion
+        Assert.Equal("at <anonymous>:1:56", logMessage?.Trim());
     }
     }
 }
 }

+ 4 - 2
Jint/Engine.cs

@@ -138,6 +138,8 @@ namespace Jint
         // having a proper execution context established
         // having a proper execution context established
         internal Realm? _realmInConstruction;
         internal Realm? _realmInConstruction;
 
 
+        internal SyntaxElement? _lastSyntaxElement;
+
         public Realm Realm => _realmInConstruction ?? ExecutionContext.Realm;
         public Realm Realm => _realmInConstruction ?? ExecutionContext.Realm;
 
 
         internal GlobalSymbolRegistry GlobalSymbolRegistry { get; } = new();
         internal GlobalSymbolRegistry GlobalSymbolRegistry { get; } = new();
@@ -735,9 +737,9 @@ namespace Jint
         /// <summary>
         /// <summary>
         /// Gets the last evaluated <see cref="Node"/>.
         /// Gets the last evaluated <see cref="Node"/>.
         /// </summary>
         /// </summary>
-        internal SyntaxElement? GetLastSyntaxElement()
+        internal SyntaxElement GetLastSyntaxElement()
         {
         {
-            return _activeEvaluationContext?.LastSyntaxElement;
+            return _lastSyntaxElement!;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 8 - 1
Jint/Runtime/Interpreter/EvaluationContext.cs

@@ -31,7 +31,14 @@ internal sealed class EvaluationContext
     public readonly Completion ResumedCompletion;
     public readonly Completion ResumedCompletion;
     public bool DebugMode => Engine._isDebugMode;
     public bool DebugMode => Engine._isDebugMode;
 
 
-    public SyntaxElement LastSyntaxElement = null!;
+    public SyntaxElement LastSyntaxElement
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        get => Engine.GetLastSyntaxElement();
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        set => Engine._lastSyntaxElement = value;
+    }
+
     public readonly bool OperatorOverloadingAllowed;
     public readonly bool OperatorOverloadingAllowed;
 
 
     // completion record information
     // completion record information