diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index 5aecb19cdc..1e606c464b 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp3.0;net452 + netcoreapp3.0;net46 @@ -9,7 +9,7 @@ - + diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index a338a51d39..dc1aa7a25c 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -1,7 +1,9 @@ using System; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Esprima; using Esprima.Ast; using Jint.Native; @@ -726,6 +728,49 @@ public void ShouldThrowStatementCountOverflow() ); } + [Fact] + public async Task ShouldHandleConcurrentCallsUsingInvoke() + { + + var script = @"var recurse = function(n) { + if (n <= 0) { + return; + } + recurse(n - 1) + };"; + + var engine = new Engine(cfg => cfg.LimitRecursion(10)); + engine.Execute(script); + + // Should not throw + await Task.WhenAll(Enumerable.Range(0, 20).Select(async _ => + { + await Task.Yield(); + engine.Invoke("recurse", 10); + })); + } + + [Fact] + public async Task ShouldHandleConcurrentCallsUsingExecute() + { + var script = @"var recurse = function(n) { + if (n <= 0) { + return; + } + recurse(n - 1) + };"; + + var engine = new Engine(cfg => cfg.LimitRecursion(10)); + engine.Execute(script); + + // Should not throw + await Task.WhenAll(Enumerable.Range(0, 20).Select(async _ => + { + await Task.Yield(); + engine.Execute("recurse(10)"); + })); + } + [Fact] public void ShouldThrowMemoryLimitExceeded() { diff --git a/Jint/Engine.cs b/Jint/Engine.cs index d7ab631689..99740b5abd 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using Esprima; using Esprima.Ast; using Jint.Native; @@ -30,6 +33,7 @@ using Jint.Runtime.Interop; using Jint.Runtime.Interpreter; using Jint.Runtime.References; +using ExecutionContext = Jint.Runtime.Environments.ExecutionContext; namespace Jint { @@ -51,7 +55,6 @@ public class Engine private static readonly JsString _typeErrorFunctionName = new JsString("TypeError"); private static readonly JsString _uriErrorFunctionName = new JsString("URIError"); - private readonly ExecutionContextStack _executionContexts; private JsValue _completionValue = JsValue.Undefined; private int _statementsCount; private long _initialMemoryUsage; @@ -83,7 +86,7 @@ public class Engine public ITypeConverter ClrTypeConverter { get; set; } // cache of types used when resolving CLR type names - internal readonly Dictionary TypeCache = new Dictionary(); + internal readonly IDictionary TypeCache = new ConcurrentDictionary(); internal static Dictionary> TypeMappers = new Dictionary> { @@ -143,10 +146,17 @@ public override int GetHashCode() } } - internal readonly Dictionary> ClrPropertyDescriptorFactories = - new Dictionary>(); + internal readonly IDictionary> ClrPropertyDescriptorFactories = + new ConcurrentDictionary>(); + + + private AsyncLocal _callStackLocal = new AsyncLocal(); + internal JintCallStack CallStack { get => _callStackLocal.Value; private set => _callStackLocal.Value = value; } + + + private AsyncLocal _executionContextLocal = new AsyncLocal(); + public ExecutionContext ExecutionContext { get => _executionContextLocal.Value; private set => _executionContextLocal.Value = value; } - internal readonly JintCallStack CallStack = new JintCallStack(); static Engine() { @@ -154,18 +164,55 @@ static Engine() if (methodInfo != null) { - GetAllocatedBytesForCurrentThread = (Func)Delegate.CreateDelegate(typeof(Func), null, methodInfo); + GetAllocatedBytesForCurrentThread = (Func)Delegate.CreateDelegate(typeof(Func), null, methodInfo); } } + internal Task WithExecutionContext( + LexicalEnvironment lexicalEnvironment, + LexicalEnvironment variableEnvironment, + JsValue thisBinding, + Func call) + { + return WithExecutionContext( + lexicalEnvironment, + variableEnvironment, + thisBinding, + () => Task.FromResult(call())); + } + + internal async Task WithExecutionContext( + LexicalEnvironment lexicalEnvironment, + LexicalEnvironment variableEnvironment, + JsValue thisBinding, + Func> call) + { + var context = new ExecutionContext( + lexicalEnvironment, + variableEnvironment, + thisBinding); + + ExecutionContext = context; + return await call().ConfigureAwait(false); + } + + internal Task ExecuteCallAsync(CallStackElement element, Func call) + { + return ExecuteCallAsync(element, () => Task.FromResult(call())); + } + + internal async Task ExecuteCallAsync(CallStackElement element, Func> call) + { + CallStack = new JintCallStack(CallStack, element); + return await call().ConfigureAwait(false); + } + public Engine() : this(null) { } public Engine(Action options) { - _executionContexts = new ExecutionContextStack(2); - Global = GlobalObject.CreateGlobalObject(this); Object = ObjectConstructor.CreateObjectConstructor(this); @@ -198,7 +245,12 @@ public Engine(Action options) GlobalEnvironment = LexicalEnvironment.NewObjectEnvironment(this, Global, null, false); // create the global execution context http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.1.1 - EnterExecutionContext(GlobalEnvironment, GlobalEnvironment, Global); + ExecutionContext = new ExecutionContext( + GlobalEnvironment, + GlobalEnvironment, + Global); + + CallStack = new JintCallStack(null, null); Options = new Options(); @@ -210,7 +262,7 @@ public Engine(Action options) _maxStatements = Options._MaxStatements; _referenceResolver = Options.ReferenceResolver; _memoryLimit = Options._MemoryLimit; - _runBeforeStatementChecks = (_maxStatements > 0 &&_maxStatements < int.MaxValue) + _runBeforeStatementChecks = (_maxStatements > 0 && _maxStatements < int.MaxValue) || Options._TimeoutInterval.Ticks > 0 || _memoryLimit > 0 || _isDebugMode; @@ -260,12 +312,6 @@ public Engine(Action options) public ErrorConstructor ReferenceError => _referenceError ?? (_referenceError = ErrorConstructor.CreateErrorConstructor(this, _referenceErrorFunctionName)); public ErrorConstructor UriError => _uriError ?? (_uriError = ErrorConstructor.CreateErrorConstructor(this, _uriErrorFunctionName)); - public ref readonly ExecutionContext ExecutionContext - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref _executionContexts.Peek(); - } - public GlobalSymbolRegistry GlobalSymbolRegistry { get; } internal Options Options { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } @@ -293,19 +339,6 @@ public ref readonly ExecutionContext ExecutionContext private static readonly Func GetAllocatedBytesForCurrentThread; - public void EnterExecutionContext( - LexicalEnvironment lexicalEnvironment, - LexicalEnvironment variableEnvironment, - JsValue thisBinding) - { - var context = new ExecutionContext( - lexicalEnvironment, - variableEnvironment, - thisBinding); - - _executionContexts.Push(context); - } - public Engine SetValue(in Key name, Delegate value) { Global.FastAddProperty(name, new DelegateWrapper(this, value), true, false, true); @@ -342,12 +375,6 @@ public Engine SetValue(in Key name, object obj) { return SetValue(name, JsValue.FromObject(this, obj)); } - - public void LeaveExecutionContext() - { - _executionContexts.Pop(); - } - /// /// Initializes the statements count /// @@ -370,14 +397,6 @@ public void ResetTimeoutTicks() _timeoutTicks = timeoutIntervalTicks > 0 ? DateTime.UtcNow.Ticks + timeoutIntervalTicks : 0; } - /// - /// Initializes list of references of called functions - /// - public void ResetCallStack() - { - CallStack.Clear(); - } - public Engine Execute(string source) { return Execute(source, DefaultParserOptions); @@ -400,9 +419,8 @@ public Engine Execute(Program program) ResetTimeoutTicks(); ResetLastStatement(); - ResetCallStack(); - using (new StrictModeScope(_isStrict || program.Strict)) + StrictModeScope.WithStrictModeScope(() => { DeclarationBindingInstantiation( DeclarationBindingType.GlobalCode, @@ -419,7 +437,10 @@ public Engine Execute(Program program) } _completionValue = result.GetValueOrDefault(); - } + + return true; + + }, (_isStrict || program.Strict)).GetAwaiter().GetResult(); return this; } @@ -488,7 +509,7 @@ internal JsValue GetValue(object value, bool returnReferenceToPool) if (!(value is Reference reference)) { - return ((Completion) value).Value; + return ((Completion)value).Value; } return GetValue(reference, returnReferenceToPool); @@ -549,7 +570,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) return Undefined.Instance; } - var callable = (ICallable) getter.AsObject(); + var callable = (ICallable)getter.AsObject(); return callable.Call(baseValue, Arguments.Empty); } } @@ -589,7 +610,7 @@ public void PutValue(Reference reference, JsValue value) var baseValue = reference._baseValue; if (reference._baseValue._type == Types.Object || reference._baseValue._type == Types.None) { - ((ObjectInstance) baseValue).Put(referencedName, value, reference._strict); + ((ObjectInstance)baseValue).Put(referencedName, value, reference._strict); } else { @@ -599,7 +620,7 @@ public void PutValue(Reference reference, JsValue value) else { var baseValue = reference._baseValue; - ((EnvironmentRecord) baseValue).SetMutableBinding(referencedName, value, reference._strict); + ((EnvironmentRecord)baseValue).SetMutableBinding(referencedName, value, reference._strict); } } @@ -877,12 +898,6 @@ private void AddFunctionDeclarations( } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void UpdateLexicalEnvironment(LexicalEnvironment newEnv) - { - _executionContexts.ReplaceTopLexicalEnvironment(newEnv); - } - private static void AssertNotNullOrEmpty(string propertyName, string propertyValue) { if (string.IsNullOrEmpty(propertyValue)) diff --git a/Jint/EvalCodeScope.cs b/Jint/EvalCodeScope.cs index 01c449da8c..47f0603a31 100644 --- a/Jint/EvalCodeScope.cs +++ b/Jint/EvalCodeScope.cs @@ -1,62 +1,54 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace Jint { - public class EvalCodeScope : IDisposable + public class EvalCodeScope { private readonly bool _eval; private readonly bool _force; - private readonly int _forcedRefCount; + private readonly int _refCount; - [ThreadStatic] - private static int _refCount; + private static readonly AsyncLocal _currentScope = new AsyncLocal(); - public EvalCodeScope(bool eval = true, bool force = false) + public static Task WithEvalCodeScope(Func func, bool eval = true, bool force = false) + { + return WithEvalCodeScope( + () => Task.FromResult(func()), + eval, + force); + } + + public static async Task WithEvalCodeScope(Func> func, bool eval = true, bool force = false) + { + _currentScope.Value = new EvalCodeScope(eval, force); + return await func().ConfigureAwait(false); + } + + private EvalCodeScope(bool eval = true, bool force = false) { _eval = eval; _force = force; if (_force) { - _forcedRefCount = _refCount; _refCount = 0; } - - if (_eval) + else { - _refCount++; + _refCount = _currentScope?.Value?._refCount ?? 0; } - } - - public void Dispose() - { if (_eval) { - _refCount--; + _refCount++; } - if (_force) - { - _refCount = _forcedRefCount; - } } - public static bool IsEvalCode - { - get { return _refCount > 0; } - } + public static bool IsEvalCode => _currentScope?.Value?._refCount > 0; - public static int RefCount - { - get - { - return _refCount; - } - set - { - _refCount = value; - } - } + public static int RefCount => _currentScope?.Value?._refCount ?? 0; } } diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index a0c91e43a9..679a1d0f3c 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -1,7 +1,7 @@  en-US - netstandard2.0;net45 + netstandard2.0;net46 Jint.snk true latest diff --git a/Jint/Native/Function/ArrowFunctionInstance.cs b/Jint/Native/Function/ArrowFunctionInstance.cs index bc7750ef0f..a7b9064e28 100644 --- a/Jint/Native/Function/ArrowFunctionInstance.cs +++ b/Jint/Native/Function/ArrowFunctionInstance.cs @@ -29,7 +29,7 @@ internal ArrowFunctionInstance( JintFunctionDefinition function, LexicalEnvironment scope, bool strict) - : base(engine, (string) null, function._parameterNames, scope, strict) + : base(engine, (string)null, function._parameterNames, scope, strict) { _function = function; @@ -54,48 +54,44 @@ public override JsValue Call(JsValue thisArg, JsValue[] arguments) var localEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _scope); var strict = Strict || _engine._isStrict; - using (new StrictModeScope(strict, true)) + return StrictModeScope.WithStrictModeScope(() => { - _engine.EnterExecutionContext( + return _engine.WithExecutionContext( localEnv, localEnv, - _thisBinding); - - try - { - var argumentInstanceRented = _engine.DeclarationBindingInstantiation( + _thisBinding, + () => + { + var argumentInstanceRented = _engine.DeclarationBindingInstantiation( DeclarationBindingType.FunctionCode, _function._hoistingScope, functionInstance: this, arguments); - var result = _function._body.Execute(); + var result = _function._body.Execute(); - var value = result.GetValueOrDefault(); + var value = result.GetValueOrDefault(); - if (argumentInstanceRented) - { - _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled(); - _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled(); - } + if (argumentInstanceRented) + { + _engine.ExecutionContext.LexicalEnvironment?._record?.FunctionWasCalled(); + _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled(); + } - if (result.Type == CompletionType.Throw) - { - ExceptionHelper.ThrowJavaScriptException(_engine, value, result); - } + if (result.Type == CompletionType.Throw) + { + ExceptionHelper.ThrowJavaScriptException(_engine, value, result); + } - if (result.Type == CompletionType.Return) - { - return value; - } - } - finally - { - _engine.LeaveExecutionContext(); - } - - return Undefined; - } + if (result.Type == CompletionType.Return) + { + return value; + } + + return Undefined; + }); + + }, strict, true).GetAwaiter().GetResult(); } public override void Put(in Key propertyName, JsValue value, bool throwOnError) @@ -114,7 +110,7 @@ public override JsValue Get(in Key propertyName) private void AssertValidPropertyName(in Key propertyName) { if (propertyName == KnownKeys.Caller - || propertyName == KnownKeys.Callee + || propertyName == KnownKeys.Callee || propertyName == KnownKeys.Arguments) { ExceptionHelper.ThrowTypeError(_engine, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them"); diff --git a/Jint/Native/Function/EvalFunctionInstance.cs b/Jint/Native/Function/EvalFunctionInstance.cs index d8a9c344b9..0c44a410f5 100644 --- a/Jint/Native/Function/EvalFunctionInstance.cs +++ b/Jint/Native/Function/EvalFunctionInstance.cs @@ -1,4 +1,6 @@ -using Esprima; +using System; +using Esprima; +using Esprima.Ast; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; @@ -11,7 +13,7 @@ public sealed class EvalFunctionInstance : FunctionInstance private static readonly ParserOptions ParserOptions = new ParserOptions { AdaptRegexp = true, Tolerant = false }; private static readonly JsString _functionName = new JsString("eval"); - public EvalFunctionInstance(Engine engine, string[] parameters, LexicalEnvironment scope, bool strict) + public EvalFunctionInstance(Engine engine, string[] parameters, LexicalEnvironment scope, bool strict) : base(engine, _functionName, parameters, scope, strict) { Prototype = Engine.Function.PrototypeObject; @@ -37,77 +39,87 @@ public JsValue Call(JsValue thisObject, JsValue[] arguments, bool directCall) { var parser = new JavaScriptParser(code, ParserOptions); var program = parser.ParseProgram(StrictModeScope.IsStrictModeCode); - using (new StrictModeScope(program.Strict)) + return StrictModeScope.WithStrictModeScope(() => { - using (new EvalCodeScope()) + return EvalCodeScope.WithEvalCodeScope(() => { - LexicalEnvironment strictVarEnv = null; - - try + if (!directCall) { - if (!directCall) - { - Engine.EnterExecutionContext(Engine.GlobalEnvironment, Engine.GlobalEnvironment, Engine.Global); - } - - var lexicalEnvironment = _engine.ExecutionContext.LexicalEnvironment; - if (StrictModeScope.IsStrictModeCode) - { - strictVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(Engine, lexicalEnvironment); - Engine.EnterExecutionContext(strictVarEnv, strictVarEnv, Engine.ExecutionContext.ThisBinding); - } - - bool argumentInstanceRented = Engine.DeclarationBindingInstantiation( - DeclarationBindingType.EvalCode, - program.HoistingScope, - functionInstance: this, - arguments); - - var statement = JintStatement.Build(_engine, program); - var result = statement.Execute(); - var value = result.GetValueOrDefault(); - - if (argumentInstanceRented) - { - lexicalEnvironment?._record?.FunctionWasCalled(); - _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled(); - } - - if (result.Type == CompletionType.Throw) - { - var ex = new JavaScriptException(value).SetCallstack(_engine, result.Location); - throw ex; - } - else - { - return value; - } + return Engine.WithExecutionContext( + Engine.GlobalEnvironment, + Engine.GlobalEnvironment, + Engine.Global, + () => ExecuteCallWithStrictCheck(program, arguments)).GetAwaiter().GetResult(); } - finally + else { - if (strictVarEnv != null) - { - Engine.LeaveExecutionContext(); - } - - if (!directCall) - { - Engine.LeaveExecutionContext(); - } + return ExecuteCallWithStrictCheck(program, arguments); } - } - } + }).GetAwaiter().GetResult(); + + }, program.Strict).GetAwaiter().GetResult(); } catch (ParserException e) { if (e.Description == Messages.InvalidLHSInAssignment) { - ExceptionHelper.ThrowReferenceError(_engine, (string) null); + ExceptionHelper.ThrowReferenceError(_engine, (string)null); } ExceptionHelper.ThrowSyntaxError(_engine); return null; } } + + private JsValue ExecuteCallWithStrictCheck(Program program, JsValue[] arguments) + { + var lexical = Engine.ExecutionContext.LexicalEnvironment; + + if (StrictModeScope.IsStrictModeCode) + { + var strictVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(Engine, lexical); + return Engine + .WithExecutionContext( + strictVarEnv, + strictVarEnv, + Engine.ExecutionContext.ThisBinding, + () => ExecuteCall(program, arguments, lexical)) + .GetAwaiter() + .GetResult(); + } + else + { + return ExecuteCall(program, arguments, lexical); + } + } + + private JsValue ExecuteCall(Program program, JsValue[] arguments, LexicalEnvironment lexical) + { + bool argumentInstanceRented = Engine.DeclarationBindingInstantiation( + DeclarationBindingType.EvalCode, + program.HoistingScope, + functionInstance: this, + arguments); + + var statement = JintStatement.Build(_engine, program); + var result = statement.Execute(); + var value = result.GetValueOrDefault(); + + if (argumentInstanceRented) + { + lexical?._record?.FunctionWasCalled(); + _engine.ExecutionContext.VariableEnvironment?._record?.FunctionWasCalled(); + } + + if (result.Type == CompletionType.Throw) + { + var ex = new JavaScriptException(value).SetCallstack(_engine, result.Location); + throw ex; + } + else + { + return value; + } + } } } diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index 29fd7808e5..1d9b3afa59 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -65,7 +65,7 @@ internal ScriptFunctionInstance( public override JsValue Call(JsValue thisArg, JsValue[] arguments) { var strict = _strict || _engine._isStrict; - using (new StrictModeScope(strict, true)) + return StrictModeScope.WithStrictModeScope(() => { // setup new execution context http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.3 JsValue thisBinding; @@ -88,9 +88,7 @@ public override JsValue Call(JsValue thisArg, JsValue[] arguments) var localEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _scope); - _engine.EnterExecutionContext(localEnv, localEnv, thisBinding); - - try + return _engine.WithExecutionContext(localEnv, localEnv, thisBinding, () => { var argumentInstanceRented = _engine.DeclarationBindingInstantiation( DeclarationBindingType.FunctionCode, @@ -117,14 +115,12 @@ public override JsValue Call(JsValue thisArg, JsValue[] arguments) { return value; } - } - finally - { - _engine.LeaveExecutionContext(); - } - return Undefined; - } + return Undefined; + + }).GetAwaiter().GetResult(); + + }, strict, true).GetAwaiter().GetResult(); } /// diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index f97c9e13e3..74ff483720 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -25,6 +25,7 @@ public class ObjectInstance : JsValue, IEquatable internal StringDictionarySlim _properties; + private object _initializeLock = new object(); private bool _initialized; private readonly string _class; protected readonly Engine _engine; @@ -748,9 +749,15 @@ protected void EnsureInitialized() { if (!_initialized) { - // we need to set flag eagerly to prevent wrong recursion - _initialized = true; - Initialize(); + lock (_initializeLock) + { + if (!_initialized) + { + // we need to set flag eagerly to prevent wrong recursion + _initialized = true; + Initialize(); + } + } } } diff --git a/Jint/Pooling/ArgumentsInstancePool.cs b/Jint/Pooling/ArgumentsInstancePool.cs index bf017bc8b0..0ebf881362 100644 --- a/Jint/Pooling/ArgumentsInstancePool.cs +++ b/Jint/Pooling/ArgumentsInstancePool.cs @@ -13,12 +13,12 @@ internal sealed class ArgumentsInstancePool { private const int PoolSize = 10; private readonly Engine _engine; - private readonly ObjectPool _pool; + private readonly ConcurrentObjectPool _pool; public ArgumentsInstancePool(Engine engine) { _engine = engine; - _pool = new ObjectPool(Factory, PoolSize); + _pool = new ConcurrentObjectPool(Factory, PoolSize); } private ArgumentsInstance Factory() diff --git a/Jint/Pooling/JsValueArrayPool.cs b/Jint/Pooling/JsValueArrayPool.cs index 6783b607ac..1058a4774b 100644 --- a/Jint/Pooling/JsValueArrayPool.cs +++ b/Jint/Pooling/JsValueArrayPool.cs @@ -10,15 +10,15 @@ namespace Jint.Pooling internal sealed class JsValueArrayPool { private const int PoolSize = 15; - private readonly ObjectPool _poolArray1; - private readonly ObjectPool _poolArray2; - private readonly ObjectPool _poolArray3; + private readonly ConcurrentObjectPool _poolArray1; + private readonly ConcurrentObjectPool _poolArray2; + private readonly ConcurrentObjectPool _poolArray3; public JsValueArrayPool() { - _poolArray1 = new ObjectPool(Factory1, PoolSize); - _poolArray2 = new ObjectPool(Factory2, PoolSize); - _poolArray3 = new ObjectPool(Factory3, PoolSize); + _poolArray1 = new ConcurrentObjectPool(Factory1, PoolSize); + _poolArray2 = new ConcurrentObjectPool(Factory2, PoolSize); + _poolArray3 = new ConcurrentObjectPool(Factory3, PoolSize); } private static JsValue[] Factory1() diff --git a/Jint/Pooling/ReferencePool.cs b/Jint/Pooling/ReferencePool.cs index c25156e0f6..e20103755a 100644 --- a/Jint/Pooling/ReferencePool.cs +++ b/Jint/Pooling/ReferencePool.cs @@ -9,11 +9,11 @@ namespace Jint.Pooling internal sealed class ReferencePool { private const int PoolSize = 10; - private readonly ObjectPool _pool; + private readonly ConcurrentObjectPool _pool; public ReferencePool() { - _pool = new ObjectPool(Factory, PoolSize); + _pool = new ConcurrentObjectPool(Factory, PoolSize); } private static Reference Factory() diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index e212e6eb21..a3025f5565 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -1,60 +1,59 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace Jint.Runtime.CallStack { public class JintCallStack : IEnumerable { - private readonly Stack _stack = new Stack(); + internal JintCallStack Parent; + private CallStackElement _element; - private readonly Dictionary _statistics = - new Dictionary(new CallStackElementComparer()); + private readonly IDictionary _statistics; - public int Push(CallStackElement item) + + internal JintCallStack(JintCallStack parent, CallStackElement element) { - _stack.Push(item); - if (_statistics.ContainsKey(item)) - { - return ++_statistics[item]; - } - else + Parent = parent; + _element = element; + _statistics = parent == null + ? new ConcurrentDictionary(new CallStackElementComparer()) + : new ConcurrentDictionary(parent._statistics, new CallStackElementComparer()); + + if (element != null) { - _statistics.Add(item, 0); - return 0; + if (!_statistics.TryGetValue(element, out int i)) + { + i = -1; + } + + _statistics[element] = i + 1; } } - public CallStackElement Pop() + public int Depth => _statistics[_element]; + + public IEnumerator GetEnumerator() { - var item = _stack.Pop(); - if (_statistics[item] == 0) + var current = this; + var stack = new List(); + while (current?._element != null) { - _statistics.Remove(item); + stack.Add(current._element); + current = current.Parent; } - else - { - _statistics[item]--; - } - - return item; - } - public void Clear() - { - _stack.Clear(); - _statistics.Clear(); + return stack.GetEnumerator(); } - public IEnumerator GetEnumerator() - { - return _stack.GetEnumerator(); - } public override string ToString() { - return string.Join("->", _stack.Select(cse => cse.ToString()).Reverse()); + return string.Join("->", this.Select(cse => cse.ToString()).Reverse()); } IEnumerator IEnumerable.GetEnumerator() diff --git a/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs b/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs index e6996bbcd1..a8d70c785d 100644 --- a/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs @@ -418,11 +418,13 @@ JsValue RunInNewParameterEnvironment(JintExpression exp) var oldEnv = _engine.ExecutionContext.LexicalEnvironment; var paramVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv); - _engine.EnterExecutionContext(paramVarEnv, paramVarEnv, _engine.ExecutionContext.ThisBinding);; - var result = exp.GetValue(); - _engine.LeaveExecutionContext(); - - return result; + return _engine + .WithExecutionContext( + paramVarEnv, + paramVarEnv, + _engine.ExecutionContext.ThisBinding, + () => exp.GetValue()) + .GetAwaiter().GetResult(); } var expression = assignmentPattern.Right.As(); diff --git a/Jint/Runtime/Environments/ExecutionContext.cs b/Jint/Runtime/Environments/ExecutionContext.cs index cf7c74c2eb..dc1959ce5a 100644 --- a/Jint/Runtime/Environments/ExecutionContext.cs +++ b/Jint/Runtime/Environments/ExecutionContext.cs @@ -2,22 +2,21 @@ namespace Jint.Runtime.Environments { - public readonly struct ExecutionContext + public class ExecutionContext { - public ExecutionContext(LexicalEnvironment lexicalEnvironment, LexicalEnvironment variableEnvironment, JsValue thisBinding) + public ExecutionContext( + LexicalEnvironment lexicalEnvironment, + LexicalEnvironment variableEnvironment, + JsValue thisBinding) { LexicalEnvironment = lexicalEnvironment; VariableEnvironment = variableEnvironment; ThisBinding = thisBinding; } - public readonly LexicalEnvironment LexicalEnvironment; - public readonly LexicalEnvironment VariableEnvironment; - public readonly JsValue ThisBinding; + public LexicalEnvironment LexicalEnvironment { get; } + public LexicalEnvironment VariableEnvironment { get; } + public JsValue ThisBinding { get; } - public ExecutionContext UpdateLexicalEnvironment(LexicalEnvironment newEnv) - { - return new ExecutionContext(newEnv, VariableEnvironment, ThisBinding); - } } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index 4c44ec73e8..0be0a9f480 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Esprima.Ast; using Jint.Native; using Jint.Native.Function; @@ -106,23 +107,30 @@ protected override object EvaluateInternal() } } - var func = _engine.GetValue(callee, false); var r = callee as Reference; if (_maxRecursionDepth >= 0) { - var stackItem = new CallStackElement(expression, func, r?.GetReferencedName() ?? "anonymous function"); - - var recursionDepth = _engine.CallStack.Push(stackItem); + return _engine.ExecuteCallAsync(new CallStackElement(expression, func, r?.GetReferencedName() ?? "anonymous function"), + () => + { + if (_engine.CallStack.Depth > _maxRecursionDepth) + { + ExceptionHelper.ThrowRecursionDepthOverflowException(_engine.CallStack.Parent, _engine.CallStack.Last().ToString()); + } - if (recursionDepth > _maxRecursionDepth) - { - _engine.CallStack.Pop(); - ExceptionHelper.ThrowRecursionDepthOverflowException(_engine.CallStack, stackItem.ToString()); - } + return Invoke(callee, arguments, ref func, r); + }).GetAwaiter().GetResult(); + } + else + { + return Invoke(callee, arguments, ref func, r); } + } + private object Invoke(object callee, JsValue[] arguments, ref JsValue func, Reference r) + { if (func._type == Types.Undefined) { ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'"); @@ -152,7 +160,7 @@ protected override object EvaluateInternal() } else { - var env = (EnvironmentRecord) r._baseValue; + var env = (EnvironmentRecord)r._baseValue; thisObject = env.ImplicitThisValue(); } @@ -172,11 +180,6 @@ protected override object EvaluateInternal() _engine.DebugHandler.PopDebugCallStack(); } - if (_maxRecursionDepth >= 0) - { - _engine.CallStack.Pop(); - } - if (!_cached && arguments.Length > 0) { _engine._jsValueArrayPool.ReturnArray(arguments); diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index c2fbc1a155..76907b9ec3 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -12,6 +12,7 @@ internal abstract class JintExpression { // require sub-classes to set to false explicitly to skip virtual call protected bool _initialized = true; + protected object _initializeLock = new object(); protected readonly Engine _engine; protected internal readonly INode _expression; @@ -38,8 +39,14 @@ public object Evaluate() _engine._lastSyntaxNode = _expression; if (!_initialized) { - Initialize(); - _initialized = true; + lock (_initializeLock) + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + } } return EvaluateInternal(); } diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index b99241fd99..02e272b06d 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -18,6 +18,7 @@ private class Pair private Pair[] _jintStatements; private bool _initialized; + private object _initializedLock = new object(); public JintStatementList(Engine engine, Statement statement, NodeList statements) { @@ -45,8 +46,14 @@ public Completion Execute() { if (!_initialized) { - Initialize(); - _initialized = true; + lock (_initializedLock) + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + } } if (_statement != null) diff --git a/Jint/Runtime/Interpreter/Statements/JintStatement.cs b/Jint/Runtime/Interpreter/Statements/JintStatement.cs index 17cd55a7ba..e4fc507229 100644 --- a/Jint/Runtime/Interpreter/Statements/JintStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintStatement.cs @@ -22,6 +22,7 @@ internal abstract class JintStatement // require sub-classes to set to false explicitly to skip virtual call protected bool _initialized = true; + private readonly object _initializedLock = new object(); protected JintStatement(Engine engine, Statement statement) { @@ -41,8 +42,14 @@ public Completion Execute() if (!_initialized) { - Initialize(); - _initialized = true; + lock (_initializedLock) + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + } } return ExecuteInternal(); diff --git a/Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs b/Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs index a6f22389a6..dbfb81def4 100644 --- a/Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs +++ b/Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs @@ -9,8 +9,11 @@ internal sealed class JintSwitchBlock { private readonly Engine _engine; private readonly NodeList _switchBlock; + private JintSwitchCase[] _jintSwitchBlock; + private bool _initialized; + private readonly object _initializedLock = new object(); public JintSwitchBlock(Engine engine, NodeList switchBlock) { @@ -31,8 +34,14 @@ public Completion Execute(JsValue input) { if (!_initialized) { - Initialize(); - _initialized = true; + lock (_initializedLock) + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + } } JsValue v = Undefined.Instance; diff --git a/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs index 87838f5380..6cd89d6fc5 100644 --- a/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintTryStatement.cs @@ -37,13 +37,14 @@ protected override Completion ExecuteInternal() if (_catch != null) { var c = b.Value; - var oldEnv = _engine.ExecutionContext.LexicalEnvironment; - var catchEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv); - catchEnv._record.CreateMutableBinding(_catchParamName, c); + var oldEnv = _engine.ExecutionContext; + var lex = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv.LexicalEnvironment); + lex._record.CreateMutableBinding(_catchParamName, c); + b = _engine.WithExecutionContext(lex, oldEnv.VariableEnvironment, oldEnv.ThisBinding, () => + { + return _catch.Execute(); + }).GetAwaiter().GetResult(); - _engine.UpdateLexicalEnvironment(catchEnv); - b = _catch.Execute(); - _engine.UpdateLexicalEnvironment(oldEnv); } } diff --git a/Jint/Runtime/Interpreter/Statements/JintWithStatement.cs b/Jint/Runtime/Interpreter/Statements/JintWithStatement.cs index 31067553f4..0004205b2f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintWithStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintWithStatement.cs @@ -22,25 +22,22 @@ protected override Completion ExecuteInternal() { var jsValue = _object.GetValue(); var obj = TypeConverter.ToObject(_engine, jsValue); - var oldEnv = _engine.ExecutionContext.LexicalEnvironment; - var newEnv = LexicalEnvironment.NewObjectEnvironment(_engine, obj, oldEnv, true); - _engine.UpdateLexicalEnvironment(newEnv); + var oldEnv = _engine.ExecutionContext; - Completion c; - try - { - c = _body.Execute(); - } - catch (JavaScriptException e) - { - c = new Completion(CompletionType.Throw, e.Error, null, _statement.Location); - } - finally - { - _engine.UpdateLexicalEnvironment(oldEnv); - } - - return c; + return _engine.WithExecutionContext( + LexicalEnvironment.NewObjectEnvironment(_engine, obj, oldEnv.LexicalEnvironment, true), + oldEnv.VariableEnvironment, + oldEnv.ThisBinding, () => + { + try + { + return _body.Execute(); + } + catch (JavaScriptException e) + { + return new Completion(CompletionType.Throw, e.Error, null, _statement.Location); + } + }).GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/Jint/Runtime/JavaScriptException.cs b/Jint/Runtime/JavaScriptException.cs index 198289fee9..979677f306 100644 --- a/Jint/Runtime/JavaScriptException.cs +++ b/Jint/Runtime/JavaScriptException.cs @@ -34,10 +34,15 @@ public JavaScriptException(JsValue error) Error = error; } - public JavaScriptException SetCallstack(Engine engine, Location? location = null) + public JavaScriptException SetCallstack(Engine engine, Location? location = null, bool overrideExisting = false) { Location = location ?? default; + if (!overrideExisting && CallStack != null) + { + return this; + } + using (var sb = StringBuilderPool.Rent()) { foreach (var cse in engine.CallStack) diff --git a/Jint/Runtime/RefStack.cs b/Jint/Runtime/RefStack.cs deleted file mode 100644 index e11e5bbd3a..0000000000 --- a/Jint/Runtime/RefStack.cs +++ /dev/null @@ -1,92 +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 = ArrayExt.Empty(); - } - } - } - - public void ReplaceTopLexicalEnvironment(LexicalEnvironment newEnv) - { - _array[_size - 1] = _array[_size - 1].UpdateLexicalEnvironment(newEnv); - } - } -} \ No newline at end of file diff --git a/Jint/StrictModeScope.cs b/Jint/StrictModeScope.cs index fc13ee1f8b..b57fa1b485 100644 --- a/Jint/StrictModeScope.cs +++ b/Jint/StrictModeScope.cs @@ -1,29 +1,43 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace Jint { - public readonly struct StrictModeScope : IDisposable + public readonly struct StrictModeScope { private readonly bool _strict; private readonly bool _force; - private readonly int _forcedRefCount; + private readonly int _refCount; - [ThreadStatic] - private static int _refCount; + private static readonly AsyncLocal _currentScope = new AsyncLocal(); - public StrictModeScope(bool strict = true, bool force = false) + public static Task WithStrictModeScope(Func func, bool strict = true, bool force = false) + { + return WithStrictModeScope( + () => Task.FromResult(func()), + strict, + force); + } + + public static async Task WithStrictModeScope(Func> func, bool strict = true, bool force = false) + { + _currentScope.Value = new StrictModeScope(strict, force); + return await func().ConfigureAwait(false); + } + + private StrictModeScope(bool strict = true, bool force = false) { _strict = strict; _force = force; if (_force) { - _forcedRefCount = _refCount; _refCount = 0; } else { - _forcedRefCount = 0; + _refCount = _currentScope?.Value._refCount ?? 0; } if (_strict) @@ -32,19 +46,6 @@ public StrictModeScope(bool strict = true, bool force = false) } } - public void Dispose() - { - if (_strict) - { - _refCount--; - } - - if (_force) - { - _refCount = _forcedRefCount; - } - } - - public static bool IsStrictModeCode => _refCount > 0; + public static bool IsStrictModeCode => (_currentScope?.Value._refCount ?? 0) > 0; } }