Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"async-iteration",
"Atomics",
"decorators",
"explicit-resource-management",
"import-defer",
"iterator-helpers",
"iterator-sequencing",
Expand All @@ -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",
Expand Down
64 changes: 64 additions & 0 deletions Jint.Tests/Runtime/InteropDisposeTests.cs
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 12 additions & 2 deletions Jint/AstExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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");
}
}
}
}
4 changes: 3 additions & 1 deletion Jint/Engine.Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
12 changes: 6 additions & 6 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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
{
Expand All @@ -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);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Jint/Jint.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' != 'net462' and '$(TargetFramework)' != 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR;SUPPORTS_ASYNC_DISPOSE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
Expand Down
7 changes: 0 additions & 7 deletions Jint/Native/Disposable/DisposeCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

namespace Jint.Native.Disposable;

internal enum DisposeHint
{
Normal,
Sync,
Async,
}

internal sealed class DisposeCapability
{
private readonly Engine _engine;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Function/ClassDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion Jint/Native/Function/ScriptFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion Jint/Native/Object/ObjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions Jint/ParsingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -106,4 +107,4 @@ internal ParserOptions GetParserOptions() => ReferenceEquals(this, Default)

internal ParserOptions GetParserOptions(Options engineOptions)
=> ApplyTo(_defaultParserOptions, _defaultParserOptions.RegExpParseMode, engineOptions.Constraints.RegexTimeout);
}
}
29 changes: 26 additions & 3 deletions Jint/Runtime/Environments/DeclarativeEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Runtime.CompilerServices;
using Jint.Collections;
using Jint.Native;
using Jint.Native.Disposable;

namespace Jint.Runtime.Environments;

Expand All @@ -13,6 +14,7 @@ internal class DeclarativeEnvironment : Environment
{
internal HybridDictionary<Binding>? _dictionary;
internal readonly bool _catchEnvironment;
private DisposeCapability? _disposeCapability;

public DeclarativeEnvironment(Engine engine, bool catchEnvironment = false) : base(engine)
{
Expand All @@ -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<Binding>();
_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<Binding>();
_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)
Expand All @@ -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<Binding>();
_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);
Expand Down Expand Up @@ -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;
Expand All @@ -185,6 +201,13 @@ internal void TransferTo(List<Key> 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
Expand Down
5 changes: 4 additions & 1 deletion Jint/Runtime/Environments/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ protected Environment(Engine engine) : base(InternalTypes.ObjectEnvironmentRecor
/// </summary>
/// <param name="name">The text of the bound name</param>
/// <param name="value">The value for the binding.</param>
internal abstract void InitializeBinding(Key name, JsValue value);
/// <param name="hint">Disposal type hint</param>
internal abstract void InitializeBinding(Key name, JsValue value, DisposeHint hint);

/// <summary>
/// Sets the value of an already existing mutable binding in an environment record.
Expand Down Expand Up @@ -106,6 +107,8 @@ public override bool Equals(JsValue? other)

internal JsValue? NewTarget { get; set; }

internal virtual Completion DisposeResources(Completion c) => c;

/// <summary>
/// Helper to cache JsString/Key when environments use different lookups.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions Jint/Runtime/Environments/GlobalEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Environments/ModuleEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Environments/ObjectEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal override void CreateImmutableBinding(Key name, bool strict = true)
/// <summary>
/// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
/// </summary>
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)
{
Expand Down
Loading
Loading