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