diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 51d414a2af..49d6b9daaa 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -9,7 +9,6 @@ "async-iteration", "Atomics", "decorators", - "explicit-resource-management", "import-defer", "iterator-helpers", "iterator-sequencing", @@ -32,6 +31,9 @@ ], "ExcludedFiles": [ + // needs async-iteration + "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/*.js", + // requires rewrite of destructing towards spec "language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js", "language/expressions/assignment/destructuring/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js", diff --git a/Jint.Tests/Runtime/InteropDisposeTests.cs b/Jint.Tests/Runtime/InteropDisposeTests.cs new file mode 100644 index 0000000000..76aeb1ddb8 --- /dev/null +++ b/Jint.Tests/Runtime/InteropDisposeTests.cs @@ -0,0 +1,64 @@ +namespace Jint.Tests.Runtime; + +public class InteropDisposeTests +{ + private readonly Engine _engine; + private readonly SyncDisposable _syncDisposable = new(); + +#if NETCOREAPP + private readonly AsyncDisposable _asyncDisposable = new(); +#endif + + public InteropDisposeTests() + { + _engine = new Engine(); + _engine.SetValue("getSync", () => _syncDisposable); + +#if NETCOREAPP + _engine.SetValue("getAsync", () => _asyncDisposable); +#endif + } + + [Theory] + [InlineData("{ using temp = getSync(); }")] + [InlineData("(function x() { using temp = getSync(); })()")] + [InlineData("class X { constructor() { using temp = getSync(); } } new X();")] + [InlineData("class X { static { using temp = getSync(); } } new X();")] + [InlineData("for (let i = 0; i < 1; i++) { using temp = getSync(); }")] + public void ShouldSyncDispose(string program) + { + _engine.Execute(program); + _syncDisposable.Disposed.Should().BeTrue(); + } + + private class SyncDisposable : IDisposable + { + public bool Disposed { get; private set; } + + public void Dispose() + { + Disposed = true; + } + } + +#if NETCOREAPP + [Theory] + [InlineData("(async function x() { await using temp = getAsync(); })();")] + public void ShouldAsyncDispose(string program) + { + _engine.Evaluate(program).UnwrapIfPromise(); + _asyncDisposable.Disposed.Should().BeTrue(); + } + + private class AsyncDisposable : IAsyncDisposable + { + public bool Disposed { get; private set; } + + public ValueTask DisposeAsync() + { + Disposed = true; + return default; + } + } +#endif +} diff --git a/Jint/AstExtensions.cs b/Jint/AstExtensions.cs index 3b2a410453..1c0134be05 100644 --- a/Jint/AstExtensions.cs +++ b/Jint/AstExtensions.cs @@ -299,7 +299,7 @@ internal static void BindingInitialization( if (expression is Identifier identifier) { var catchEnvRecord = (DeclarativeEnvironment) env; - catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value); + catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value, DisposeHint.Normal); } else if (expression is DestructuringPattern pattern) { @@ -498,6 +498,16 @@ internal static void AllPrivateIdentifiersValid(this Script script, Realm realm, validator.Visit(script); } + internal static DisposeHint GetDisposeHint(this VariableDeclarationKind statement) + { + return statement switch + { + VariableDeclarationKind.AwaitUsing => DisposeHint.Async, + VariableDeclarationKind.Using => DisposeHint.Sync, + _ => DisposeHint.Normal, + }; + } + private sealed class MinimalSyntaxElement : Node { public MinimalSyntaxElement(in SourceLocation location) : base(NodeType.Unknown) @@ -545,4 +555,4 @@ private static void Throw(Realm r, PrivateIdentifier id) ExceptionHelper.ThrowSyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class"); } } -} \ No newline at end of file +} diff --git a/Jint/Engine.Defaults.cs b/Jint/Engine.Defaults.cs index f6f637dbde..2c3736379f 100644 --- a/Jint/Engine.Defaults.cs +++ b/Jint/Engine.Defaults.cs @@ -7,7 +7,9 @@ public partial class Engine internal static readonly ParserOptions BaseParserOptions = ParserOptions.Default with { EcmaVersion = EcmaVersion.ES2023, - ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups, + ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes + | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups + | ExperimentalESFeatures.ExplicitResourceManagement, Tolerant = false, }; } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index cd33faa97a..a02d351aea 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1080,11 +1080,11 @@ private void GlobalDeclarationInstantiation( if (strict) { - env.CreateImmutableBindingAndInitialize(KnownKeys.Arguments, strict: false, ao); + env.CreateImmutableBindingAndInitialize(KnownKeys.Arguments, strict: false, ao, DisposeHint.Normal); } else { - env.CreateMutableBindingAndInitialize(KnownKeys.Arguments, canBeDeleted: false, ao); + env.CreateMutableBindingAndInitialize(KnownKeys.Arguments, canBeDeleted: false, ao, DisposeHint.Normal); } } @@ -1108,7 +1108,7 @@ private void GlobalDeclarationInstantiation( for (var i = 0; i < varsToInitialize.Count; i++) { var pair = varsToInitialize[i]; - env.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, JsValue.Undefined); + env.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, JsValue.Undefined, DisposeHint.Normal); } varEnv = env; @@ -1126,7 +1126,7 @@ private void GlobalDeclarationInstantiation( { var pair = varsToInitialize[i]; var initialValue = pair.InitialValue ?? env.GetBindingValue(pair.Name, strict: false); - varEnv.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue); + varEnv.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue, DisposeHint.Normal); } } @@ -1367,7 +1367,7 @@ internal void EvalDeclarationInstantiation( if (!bindingExists) { varEnvRec.CreateMutableBinding(fn, canBeDeleted: true); - varEnvRec.InitializeBinding(fn, fo); + varEnvRec.InitializeBinding(fn, fo, DisposeHint.Normal); } else { @@ -1388,7 +1388,7 @@ internal void EvalDeclarationInstantiation( if (!bindingExists) { varEnvRec.CreateMutableBinding(vn, canBeDeleted: true); - varEnvRec.InitializeBinding(vn, JsValue.Undefined); + varEnvRec.InitializeBinding(vn, JsValue.Undefined, DisposeHint.Normal); } } } diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index 3e62322506..15e2e99349 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -27,7 +27,7 @@ - $(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR + $(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR;SUPPORTS_ASYNC_DISPOSE diff --git a/Jint/Native/Disposable/DisposeCapability.cs b/Jint/Native/Disposable/DisposeCapability.cs index b91bdbf512..8d03aef8a6 100644 --- a/Jint/Native/Disposable/DisposeCapability.cs +++ b/Jint/Native/Disposable/DisposeCapability.cs @@ -2,13 +2,6 @@ namespace Jint.Native.Disposable; -internal enum DisposeHint -{ - Normal, - Sync, - Async, -} - internal sealed class DisposeCapability { private readonly Engine _engine; diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index 2c5d34281a..a55fb52516 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -203,7 +203,7 @@ public JsValue BuildConstructor(EvaluationContext context, Environment env) if (_className is not null) { - classEnv.InitializeBinding(_className, F); + classEnv.InitializeBinding(_className, F, DisposeHint.Normal); } F._privateMethods = instancePrivateMethods; diff --git a/Jint/Native/Function/ScriptFunction.cs b/Jint/Native/Function/ScriptFunction.cs index ce4c46336a..0c86a5b3b7 100644 --- a/Jint/Native/Function/ScriptFunction.cs +++ b/Jint/Native/Function/ScriptFunction.cs @@ -59,7 +59,7 @@ internal ScriptFunction( protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments) { var strict = _functionDefinition!.Strict || _thisMode == FunctionThisMode.Strict; - using (new StrictModeScope(strict, true)) + using (new StrictModeScope(strict, force: true)) { try { @@ -76,6 +76,7 @@ protected internal override JsValue Call(JsValue thisObject, JsCallArguments arg var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); var result = _functionDefinition.EvaluateBody(context, this, arguments); + result = calleeContext.LexicalEnvironment.DisposeResources(result); if (result.Type == CompletionType.Throw) { @@ -159,6 +160,7 @@ ObjectInstance IConstructor.Construct(JsCallArguments arguments, JsValue newTarg var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); var result = _functionDefinition!.EvaluateBody(context, this, arguments); + result = constructorEnv.DisposeResources(result); // The DebugHandler needs the current execution context before the return for stepping through the return point // We exclude the empty constructor generated for classes without an explicit constructor. diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 5846ed5043..043881bce4 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -5,7 +5,6 @@ using Jint.Native.Array; using Jint.Native.BigInt; using Jint.Native.Boolean; -using Jint.Native.Disposable; using Jint.Native.Json; using Jint.Native.Number; using Jint.Native.Promise; diff --git a/Jint/ParsingOptions.cs b/Jint/ParsingOptions.cs index 82d3bbaf72..1500aa7f9e 100644 --- a/Jint/ParsingOptions.cs +++ b/Jint/ParsingOptions.cs @@ -30,11 +30,12 @@ public interface IParsingOptions bool Tolerant { get; init; } } -public sealed record class ScriptParsingOptions : IParsingOptions +public sealed record ScriptParsingOptions : IParsingOptions { private static readonly ParserOptions _defaultParserOptions = Engine.BaseParserOptions with { AllowReturnOutsideFunction = true, + AllowTopLevelUsing = true, RegExpParseMode = RegExpParseMode.AdaptToInterpreted, }; @@ -106,4 +107,4 @@ internal ParserOptions GetParserOptions() => ReferenceEquals(this, Default) internal ParserOptions GetParserOptions(Options engineOptions) => ApplyTo(_defaultParserOptions, _defaultParserOptions.RegExpParseMode, engineOptions.Constraints.RegexTimeout); -} \ No newline at end of file +} diff --git a/Jint/Runtime/Environments/DeclarativeEnvironment.cs b/Jint/Runtime/Environments/DeclarativeEnvironment.cs index 80f5d23417..8da8f0eb3e 100644 --- a/Jint/Runtime/Environments/DeclarativeEnvironment.cs +++ b/Jint/Runtime/Environments/DeclarativeEnvironment.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Jint.Collections; using Jint.Native; +using Jint.Native.Disposable; namespace Jint.Runtime.Environments; @@ -13,6 +14,7 @@ internal class DeclarativeEnvironment : Environment { internal HybridDictionary? _dictionary; internal readonly bool _catchEnvironment; + private DisposeCapability? _disposeCapability; public DeclarativeEnvironment(Engine engine, bool catchEnvironment = false) : base(engine) { @@ -35,16 +37,24 @@ internal override bool TryGetBinding(BindingName name, bool strict, [NotNullWhen return false; } - internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value) + internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value, DisposeHint hint) { _dictionary ??= new HybridDictionary(); _dictionary[name] = new Binding(value, canBeDeleted, mutable: true, strict: false); + if (hint != DisposeHint.Normal) + { + HandleDisposal(value, hint); + } } - internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value) + internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value, DisposeHint hint) { _dictionary ??= new HybridDictionary(); _dictionary[name] = new Binding(value, canBeDeleted: false, mutable: false, strict); + if (hint != DisposeHint.Normal) + { + HandleDisposal(value, hint); + } } internal sealed override void CreateMutableBinding(Key name, bool canBeDeleted = false) @@ -59,10 +69,14 @@ internal sealed override void CreateImmutableBinding(Key name, bool strict = tru _dictionary.CreateImmutableBinding(name, strict); } - internal sealed override void InitializeBinding(Key name, JsValue value) + internal sealed override void InitializeBinding(Key name, JsValue value, DisposeHint hint) { _dictionary ??= new HybridDictionary(); _dictionary.SetOrUpdateValue(name, static (current, value) => current.ChangeValue(value), value); + if (hint != DisposeHint.Normal) + { + HandleDisposal(value, hint); + } } internal sealed override void SetMutableBinding(BindingName name, JsValue value, bool strict) => SetMutableBinding(name.Key, value, strict); @@ -169,6 +183,8 @@ internal sealed override string[] GetAllBindingNames() internal override JsValue GetThisBinding() => Undefined; + internal sealed override Completion DisposeResources(Completion c) => _disposeCapability?.DisposeResources(c) ?? c; + public void Clear() { _dictionary = null; @@ -185,6 +201,13 @@ internal void TransferTo(List names, DeclarativeEnvironment env) target[bn] = new Binding(lastValue.Value, canBeDeleted: false, mutable: true, strict: false); } } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void HandleDisposal(JsValue value, DisposeHint hint) + { + _disposeCapability ??= new DisposeCapability(_engine); + _disposeCapability.AddDisposableResource(value, hint); + } } internal static class DictionaryExtensions diff --git a/Jint/Runtime/Environments/Environment.cs b/Jint/Runtime/Environments/Environment.cs index 63dd157337..b5cb9d6c7d 100644 --- a/Jint/Runtime/Environments/Environment.cs +++ b/Jint/Runtime/Environments/Environment.cs @@ -49,7 +49,8 @@ protected Environment(Engine engine) : base(InternalTypes.ObjectEnvironmentRecor /// /// The text of the bound name /// The value for the binding. - internal abstract void InitializeBinding(Key name, JsValue value); + /// Disposal type hint + internal abstract void InitializeBinding(Key name, JsValue value, DisposeHint hint); /// /// Sets the value of an already existing mutable binding in an environment record. @@ -106,6 +107,8 @@ public override bool Equals(JsValue? other) internal JsValue? NewTarget { get; set; } + internal virtual Completion DisposeResources(Completion c) => c; + /// /// Helper to cache JsString/Key when environments use different lookups. /// diff --git a/Jint/Runtime/Environments/GlobalEnvironment.cs b/Jint/Runtime/Environments/GlobalEnvironment.cs index a918a89595..d562eb4396 100644 --- a/Jint/Runtime/Environments/GlobalEnvironment.cs +++ b/Jint/Runtime/Environments/GlobalEnvironment.cs @@ -146,11 +146,11 @@ private void ThrowAlreadyDeclaredException(Key name) ExceptionHelper.ThrowTypeError(_engine.Realm, $"{name} has already been declared"); } - internal override void InitializeBinding(Key name, JsValue value) + internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint) { if (_declarativeRecord.HasBinding(name)) { - _declarativeRecord.InitializeBinding(name, value); + _declarativeRecord.InitializeBinding(name, value, hint); } else { diff --git a/Jint/Runtime/Environments/ModuleEnvironment.cs b/Jint/Runtime/Environments/ModuleEnvironment.cs index 9e8b3445f8..f2c5096310 100644 --- a/Jint/Runtime/Environments/ModuleEnvironment.cs +++ b/Jint/Runtime/Environments/ModuleEnvironment.cs @@ -31,7 +31,7 @@ internal override JsValue GetThisBinding() public void CreateImportBinding(string importName, Module module, string name) { _importBindings[importName] = new IndirectBinding(module, name); - CreateImmutableBindingAndInitialize(importName, true, JsValue.Undefined); + CreateImmutableBindingAndInitialize(importName, true, Undefined, DisposeHint.Normal); } /// diff --git a/Jint/Runtime/Environments/ObjectEnvironment.cs b/Jint/Runtime/Environments/ObjectEnvironment.cs index 123fe96d8e..81411b8292 100644 --- a/Jint/Runtime/Environments/ObjectEnvironment.cs +++ b/Jint/Runtime/Environments/ObjectEnvironment.cs @@ -113,7 +113,7 @@ internal override void CreateImmutableBinding(Key name, bool strict = true) /// /// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v /// - internal override void InitializeBinding(Key name, JsValue value) => SetMutableBinding(name, value, strict: false); + internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint) => SetMutableBinding(name, value, strict: false); internal override void SetMutableBinding(Key name, JsValue value, bool strict) { diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 5b4f6d7a12..483f4ebd51 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -46,6 +46,30 @@ internal ObjectWrapper( _prototype = engine.Intrinsics.Array.PrototypeObject; } } + + if (_typeDescriptor.IsDisposable) + { + SetProperty(GlobalSymbolRegistry.Dispose, new PropertyDescriptor(new ClrFunction(engine, "dispose", static (thisObject, _) => + { + ((thisObject as ObjectWrapper)?.Target as IDisposable)?.Dispose(); + return Undefined; + }), PropertyFlag.NonEnumerable)); + } + +#if SUPPORTS_ASYNC_DISPOSE + if (_typeDescriptor.IsAsyncDisposable) + { + SetProperty(GlobalSymbolRegistry.AsyncDispose, new PropertyDescriptor(new ClrFunction(engine, "asyncDispose", (thisObject, _) => + { + var target = ((thisObject as ObjectWrapper)?.Target as IAsyncDisposable)?.DisposeAsync(); + if (target is not null) + { + return ConvertAwaitableToPromise(engine, target); + } + return Undefined; + }), PropertyFlag.NonEnumerable)); + } +#endif } /// diff --git a/Jint/Runtime/Interop/TypeDescriptor.cs b/Jint/Runtime/Interop/TypeDescriptor.cs index 1388c54311..a19f9cebce 100644 --- a/Jint/Runtime/Interop/TypeDescriptor.cs +++ b/Jint/Runtime/Interop/TypeDescriptor.cs @@ -48,6 +48,12 @@ private TypeDescriptor( IsEnumerable = isEnumerable; + IsDisposable = type.GetInterface(nameof(IDisposable)) is not null; + +#if SUPPORTS_ASYNC_DISPOSE + IsAsyncDisposable = type.GetInterface(nameof(IAsyncDisposable)) is not null; +#endif + if (IsArrayLike) { LengthProperty = lengthProperty; @@ -69,6 +75,8 @@ private TypeDescriptor( public bool IsDictionary { get; } public bool IsStringKeyedGenericDictionary => _tryGetValueMethod is not null; public bool IsEnumerable { get; } + public bool IsDisposable { get; } + public bool IsAsyncDisposable { get; } public PropertyInfo? LengthProperty { get; } public bool Iterable => IsArrayLike || IsDictionary || IsEnumerable; diff --git a/Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs index 0e678218d9..befb04b35c 100644 --- a/Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs @@ -407,7 +407,7 @@ private static void AssignToReference( } else { - lhs.InitializeReferencedBinding(v); + lhs.InitializeReferencedBinding(v, DisposeHint.Normal); } engine._referencePool.Return(lhs); } @@ -434,7 +434,7 @@ private static void AssignToIdentifier( var lhs = engine.ResolveBinding(name, environment); if (environment is not null) { - lhs.InitializeReferencedBinding(rval); + lhs.InitializeReferencedBinding(rval, DisposeHint.Normal); } else { diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs index 1c1708a9e1..12f4ba9bf6 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs @@ -80,7 +80,7 @@ private static ScriptFunction InstantiateOrdinaryFunctionExpression(Engine engin } closure.MakeConstructor(); - funcEnv?.InitializeBinding(name!, closure); + funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal); return closure; } @@ -116,7 +116,7 @@ private static ScriptFunction InstantiateAsyncFunctionExpression(Engine engine, closure.SetFunctionName(name ?? ""); - funcEnv?.InitializeBinding(name!, closure); + funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal); return closure; } @@ -158,7 +158,7 @@ private static ScriptFunction InstantiateGeneratorFunctionExpression(Engine engi var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject); closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable)); - funcEnv?.InitializeBinding(name!, closure); + funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal); return closure; } @@ -200,7 +200,7 @@ private static ScriptFunction InstantiateAsyncGeneratorFunctionExpression(Engine var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.AsyncGeneratorFunction.PrototypeObject.PrototypeObject); closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable)); - funcEnv?.InitializeBinding(name!, closure); + funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal); return closure; } diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index e5afe72805..77bdc0978b 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -210,7 +210,7 @@ internal static void BlockDeclarationInstantiation(DeclarativeEnvironment env, D var definition = new JintFunctionDefinition(functionDeclaration); var fn = definition.Name!; var fo = env._engine.Realm.Intrinsics.Function.InstantiateFunctionObject(definition, env, privateEnv); - env.InitializeBinding(fn, fo); + env.InitializeBinding(fn, fo, DisposeHint.Normal); } } diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs index e865a3adf6..e50b99f084 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs @@ -42,12 +42,13 @@ public Completion ExecuteBlock(EvaluationContext context) Initialize(context); } + DeclarativeEnvironment? blockEnv = null; Environment? oldEnv = null; var engine = context.Engine; if (_lexicalDeclarations.Declarations.Count > 0) { oldEnv = engine.ExecutionContext.LexicalEnvironment; - var blockEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment); + blockEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment); JintStatementList.BlockDeclarationInstantiation(blockEnv, _lexicalDeclarations); engine.UpdateLexicalEnvironment(blockEnv); } @@ -62,6 +63,11 @@ public Completion ExecuteBlock(EvaluationContext context) blockValue = _statementList!.Execute(context); } + if (blockEnv != null) + { + blockValue = blockEnv.DisposeResources(blockValue); + } + if (oldEnv is not null) { engine.UpdateLexicalEnvironment(oldEnv); diff --git a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs index f13fb178c0..0ea02bbd46 100644 --- a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs @@ -26,9 +26,9 @@ protected override Completion ExecuteInternal(EvaluationContext context) var classBinding = _classDefinition._className; if (classBinding != null) { - env.InitializeBinding(classBinding, value); + env.InitializeBinding(classBinding, value, DisposeHint.Normal); } return new Completion(CompletionType.Normal, JsEmpty.Instance, _statement); } -} \ No newline at end of file +} diff --git a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs index 3d0d5dd04a..1ac10014f3 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs @@ -57,7 +57,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) if (classBinding != null) { env.CreateMutableBinding(classBinding); - env.InitializeBinding(classBinding, value); + env.InitializeBinding(classBinding, value, DisposeHint.Normal); } } else if (_functionDeclaration is not null) @@ -79,7 +79,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) functionInstance.SetFunctionName("default"); } - env.InitializeBinding("*default*", value); + env.InitializeBinding("*default*", value, DisposeHint.Normal); return Completion.Empty(); } @@ -90,7 +90,7 @@ private static void InitializeBoundName(string name, JsValue value, Environment? { if (environment is not null) { - environment.InitializeBinding(name, value); + environment.InitializeBinding(name, value, DisposeHint.Normal); } else { diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index ce7d04f4d8..4f41d6c249 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -24,6 +24,7 @@ internal sealed class JintForInForOfStatement : JintStatement private List? _tdzNames; private bool _destructuring; private LhsKind _lhsKind; + private DisposeHint _disposeHint; public JintForInForOfStatement(ForInStatement statement) : base(statement) { @@ -41,47 +42,51 @@ public JintForInForOfStatement(ForOfStatement statement) : base(statement) _iterationKind = IterationKind.Iterate; } - protected override void Initialize(EvaluationContext context) + protected override void Initialize(EvaluationContext context2) { _lhsKind = LhsKind.Assignment; - var engine = context.Engine; - if (_leftNode is VariableDeclaration variableDeclaration) + _disposeHint = DisposeHint.Normal; + switch (_leftNode) { - _lhsKind = variableDeclaration.Kind == VariableDeclarationKind.Var - ? LhsKind.VarBinding - : LhsKind.LexicalBinding; + case VariableDeclaration variableDeclaration: + { + _lhsKind = variableDeclaration.Kind == VariableDeclarationKind.Var + ? LhsKind.VarBinding + : LhsKind.LexicalBinding; - var variableDeclarationDeclaration = variableDeclaration.Declarations[0]; - var id = variableDeclarationDeclaration.Id; - if (_lhsKind == LhsKind.LexicalBinding) - { - _tdzNames = new List(1); - id.GetBoundNames(_tdzNames); - } + _disposeHint = variableDeclaration.Kind.GetDisposeHint(); - if (id is DestructuringPattern pattern) - { + var variableDeclarationDeclaration = variableDeclaration.Declarations[0]; + var id = variableDeclarationDeclaration.Id; + if (_lhsKind == LhsKind.LexicalBinding) + { + _tdzNames = new List(1); + id.GetBoundNames(_tdzNames); + } + + if (id is DestructuringPattern pattern) + { + _destructuring = true; + _assignmentPattern = pattern; + } + else + { + var identifier = (Identifier) id; + _expr = new JintIdentifierExpression(identifier); + } + + break; + } + case DestructuringPattern pattern: _destructuring = true; _assignmentPattern = pattern; - } - else - { - var identifier = (Identifier) id; - _expr = new JintIdentifierExpression(identifier); - } - } - else if (_leftNode is DestructuringPattern pattern) - { - _destructuring = true; - _assignmentPattern = pattern; - } - else if (_leftNode is MemberExpression memberExpression) - { - _expr = new JintMemberExpression(memberExpression); - } - else - { - _expr = new JintIdentifierExpression((Identifier) _leftNode); + break; + case MemberExpression memberExpression: + _expr = new JintMemberExpression(memberExpression); + break; + default: + _expr = new JintIdentifierExpression((Identifier) _leftNode); + break; } _body = new ProbablyBlockStatement(_forBody); @@ -163,7 +168,7 @@ private Completion BodyEvaluation( { while (true) { - Environment? iterationEnv = null; + DeclarativeEnvironment? iterationEnv = null; if (!iteratorRecord.TryIteratorStep(out var nextResult)) { close = true; @@ -222,7 +227,7 @@ private Completion BodyEvaluation( var reference = (Reference) lhsRef; if (lhsKind == LhsKind.LexicalBinding || _leftNode.Type == NodeType.Identifier && !reference.IsUnresolvableReference) { - reference.InitializeReferencedBinding(nextValue); + reference.InitializeReferencedBinding(nextValue, _disposeHint); } else { @@ -276,6 +281,7 @@ private Completion BodyEvaluation( } var result = stmt.Execute(context); + result = iterationEnv?.DisposeResources(result) ?? result; engine.UpdateLexicalEnvironment(oldEnv); if (!result.Value.IsEmpty) @@ -374,4 +380,4 @@ private enum IterationKind Iterate, AsyncIterate } -} \ No newline at end of file +} diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs index 3addbb27d3..96285496f6 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs @@ -27,7 +27,6 @@ public JintForStatement(ForStatement statement) : base(statement) protected override void Initialize(EvaluationContext context) { - var engine = context.Engine; _body = new ProbablyBlockStatement(_statement.Body); if (_statement.Init != null) @@ -63,7 +62,7 @@ protected override void Initialize(EvaluationContext context) protected override Completion ExecuteInternal(EvaluationContext context) { Environment? oldEnv = null; - Environment? loopEnv = null; + DeclarativeEnvironment? loopEnv = null; var engine = context.Engine; if (_boundNames != null) { @@ -87,6 +86,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) engine.UpdateLexicalEnvironment(loopEnv); } + var completion = Completion.Empty(); try { if (_initExpression != null) @@ -98,12 +98,14 @@ protected override Completion ExecuteInternal(EvaluationContext context) _initStatement?.Execute(context); } - return ForBodyEvaluation(context); + completion = ForBodyEvaluation(context); + return completion; } finally { if (oldEnv is not null) { + loopEnv!.DisposeResources(completion); engine.UpdateLexicalEnvironment(oldEnv); } } @@ -177,4 +179,4 @@ private void CreatePerIterationEnvironment(EvaluationContext context) engine.UpdateLexicalEnvironment(thisIterationEnv); } -} \ No newline at end of file +} diff --git a/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs index 32eb0c81f9..c4527698fb 100644 --- a/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs @@ -76,7 +76,7 @@ protected override Completion ExecuteInternal(EvaluationContext context) } } - lhs.InitializeReferencedBinding(value); + lhs.InitializeReferencedBinding(value, _statement.Kind.GetDisposeHint()); engine._referencePool.Return(lhs); } else if (declaration.Init != null) @@ -122,4 +122,4 @@ protected override Completion ExecuteInternal(EvaluationContext context) return Completion.Empty(); } -} \ No newline at end of file +} diff --git a/Jint/Runtime/Modules/BuilderModule.cs b/Jint/Runtime/Modules/BuilderModule.cs index cb1194e0ec..bab45ddd52 100644 --- a/Jint/Runtime/Modules/BuilderModule.cs +++ b/Jint/Runtime/Modules/BuilderModule.cs @@ -34,7 +34,7 @@ protected override void InitializeEnvironment() for (var i = 0; i < _exportBuilderDeclarations.Count; i++) { var d = _exportBuilderDeclarations[i]; - _environment.CreateImmutableBindingAndInitialize(d.Key, true, d.Value); + _environment.CreateImmutableBindingAndInitialize(d.Key, true, d.Value, DisposeHint.Normal); _localExportEntries.Add(new ExportEntry(d.Key, null, null, d.Key)); } diff --git a/Jint/Runtime/Modules/SourceTextModule.cs b/Jint/Runtime/Modules/SourceTextModule.cs index 9e1183f392..58d309700b 100644 --- a/Jint/Runtime/Modules/SourceTextModule.cs +++ b/Jint/Runtime/Modules/SourceTextModule.cs @@ -222,7 +222,7 @@ protected override void InitializeEnvironment() { var ns = GetModuleNamespace(importedModule); env.CreateImmutableBinding(ie.LocalName, strict: true); - env.InitializeBinding(ie.LocalName, ns); + env.InitializeBinding(ie.LocalName, ns, DisposeHint.Normal); } else { @@ -236,7 +236,7 @@ protected override void InitializeEnvironment() { var ns = GetModuleNamespace(resolution.Module); env.CreateImmutableBinding(ie.LocalName, strict: true); - env.InitializeBinding(ie.LocalName, ns); + env.InitializeBinding(ie.LocalName, ns, DisposeHint.Normal); } else { @@ -269,7 +269,7 @@ protected override void InitializeEnvironment() if (declaredVarNames.Add(dn)) { env.CreateMutableBinding(dn); - env.InitializeBinding(dn, Undefined); + env.InitializeBinding(dn, Undefined, d.Kind.GetDisposeHint()); } } } @@ -310,7 +310,7 @@ protected override void InitializeEnvironment() { fo.SetFunctionName("default"); } - env.InitializeBinding(fn, fo); + env.InitializeBinding(fn, fo, DisposeHint.Normal); } } @@ -327,18 +327,24 @@ internal override Completion ExecuteModule(PromiseCapability? capability = null) { using (new StrictModeScope(strict: true, force: true)) { + var result = Completion.Empty(); _engine.EnterExecutionContext(moduleContext); try { var statementList = new JintStatementList(statement: null, _source.Body); + + //Create new evaluation context when called from e.g. module tests var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine); - var result = statementList.Execute(context); //Create new evaluation context when called from e.g. module tests - return result; + + result = statementList.Execute(context); } finally { + result = _environment.DisposeResources(result); _engine.LeaveExecutionContext(); } + + return result; } } else diff --git a/Jint/Runtime/Modules/SyntheticModule.cs b/Jint/Runtime/Modules/SyntheticModule.cs index 0eca02bad8..3c0119ed87 100644 --- a/Jint/Runtime/Modules/SyntheticModule.cs +++ b/Jint/Runtime/Modules/SyntheticModule.cs @@ -67,7 +67,7 @@ protected internal override int InnerModuleLinking(Stack stack, in foreach (var exportName in _exportNames) { _environment.CreateMutableBinding(exportName, canBeDeleted: false); - _environment.InitializeBinding(exportName, Undefined); + _environment.InitializeBinding(exportName, Undefined, DisposeHint.Normal); } return index; } diff --git a/Jint/Runtime/Reference.cs b/Jint/Runtime/Reference.cs index eb1365f7a2..1a5c9e932b 100644 --- a/Jint/Runtime/Reference.cs +++ b/Jint/Runtime/Reference.cs @@ -104,9 +104,9 @@ internal void AssertValid(Realm realm) } } - internal void InitializeReferencedBinding(JsValue value) + internal void InitializeReferencedBinding(JsValue value, DisposeHint hint) { - ((Environment) _base).InitializeBinding(TypeConverter.ToString(_referencedName), value); + ((Environment) _base).InitializeBinding(TypeConverter.ToString(_referencedName), value, hint); } internal void EvaluateAndCachePropertyKey() @@ -117,3 +117,10 @@ internal void EvaluateAndCachePropertyKey() } } } + +internal enum DisposeHint +{ + Normal, + Sync, + Async, +} diff --git a/README.md b/README.md index b6c2ee689f..65b7c9b119 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ and many more. #### ECMAScript Stage 3 or earlier (no version yet) - ✔ `Error.isError` +- ✔ Explicit Resource Management (`using` and `await using`) - ✔ `Math.sumPrecise` - ✔ `ShadowRealm` - ✔ `Uint8Array` to/from base64