Browse Source

Add explicit resource management supporting types (#2143)

Marko Lahma 2 weeks ago
parent
commit
ac3f89d740

+ 21 - 5
Jint.Repl/Program.cs

@@ -4,6 +4,8 @@ using Jint.Native;
 using Jint.Native.Json;
 using Jint.Runtime;
 
+// ReSharper disable LocalizableElement
+
 #pragma warning disable IL2026
 #pragma warning disable IL2111
 
@@ -13,6 +15,7 @@ var engine = new Engine(cfg => cfg
 
 engine
     .SetValue("print", new Action<object>(Console.WriteLine))
+    .SetValue("console", new JsConsole())
     .SetValue("load", new Func<string, object>(
         path => engine.Evaluate(File.ReadAllText(path)))
     );
@@ -22,7 +25,7 @@ if (!string.IsNullOrEmpty(filename))
 {
     if (!File.Exists(filename))
     {
-        Console.WriteLine("Could not find file: {0}", filename);
+        Console.WriteLine($"Could not find file: {filename}");
     }
 
     var script = File.ReadAllText(filename);
@@ -33,10 +36,8 @@ if (!string.IsNullOrEmpty(filename))
 var assembly = Assembly.GetExecutingAssembly();
 var version = assembly.GetName().Version?.ToString();
 
-Console.WriteLine("Welcome to Jint ({0})", version);
-Console.WriteLine("Type 'exit' to leave, " +
-                  "'print()' to write on the console, " +
-                  "'load()' to load scripts.");
+Console.WriteLine($"Welcome to Jint ({version})");
+Console.WriteLine("Type 'exit' to leave, 'print()' to write on the console, 'load()' to load scripts.");
 Console.WriteLine();
 
 var defaultColor = Console.ForegroundColor;
@@ -86,3 +87,18 @@ while (true)
         Console.WriteLine(e.Message);
     }
 }
+
+file sealed class JsConsole
+{
+    public void Log(object value)
+    {
+        Console.WriteLine(value?.ToString() ?? "null");
+    }
+
+    public void Error(object value)
+    {
+        Console.ForegroundColor = ConsoleColor.Red;
+        Console.WriteLine(value?.ToString() ?? "null");
+        Console.ResetColor();
+    }
+}

+ 1 - 3
Jint.Tests/Runtime/TypedArrayInteropTests.cs

@@ -1,5 +1,3 @@
-using Jint.Native;
-
 namespace Jint.Tests.Runtime;
 
 public class TypedArrayInteropTests
@@ -136,7 +134,7 @@ public class TypedArrayInteropTests
             Assert.True(fromEngine.IsFloat16Array());
             Assert.Equal(source, fromEngine.AsFloat16Array());
 
-            engine.SetValue("testFunc", new Func<JsTypedArray, JsTypedArray>(v => v));
+            engine.SetValue("testFunc", new Func<Native.JsTypedArray, Native.JsTypedArray>(v => v));
             Assert.Equal(source, engine.Evaluate("testFunc(testSubject)").AsFloat16Array());
         }
 #endif

+ 34 - 0
Jint/Native/Disposable/AsyncDisposableStackConstructor.cs

@@ -0,0 +1,34 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.Disposable;
+
+internal sealed class AsyncDisposableStackConstructor : Constructor
+{
+    private static readonly JsString _name = new("AsyncDisposableStack");
+
+    public AsyncDisposableStackConstructor(Engine engine, Realm realm) : base(engine, realm, _name)
+    {
+        PrototypeObject = new AsyncDisposableStackPrototype(engine, realm, this, engine.Intrinsics.Object.PrototypeObject);
+        _length = new PropertyDescriptor(0, PropertyFlag.Configurable);
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+    }
+
+    internal AsyncDisposableStackPrototype PrototypeObject { get; }
+
+    public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
+    {
+        if (newTarget.IsUndefined())
+        {
+            ExceptionHelper.ThrowTypeError(_realm);
+        }
+
+        var stack = OrdinaryCreateFromConstructor(
+            newTarget,
+            static intrinsics => intrinsics.AsyncDisposableStack.PrototypeObject,
+            static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Async));
+
+        return stack;
+    }
+}

+ 101 - 0
Jint/Native/Disposable/AsyncDisposableStackPrototype.cs

@@ -0,0 +1,101 @@
+using Jint.Native.Object;
+using Jint.Native.Symbol;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.Disposable;
+
+internal sealed class AsyncDisposableStackPrototype : Prototype
+{
+    private readonly AsyncDisposableStackConstructor _constructor;
+
+    internal AsyncDisposableStackPrototype(
+        Engine engine,
+        Realm realm,
+        AsyncDisposableStackConstructor constructor,
+        ObjectPrototype objectPrototype) : base(engine, realm)
+    {
+        _prototype = objectPrototype;
+        _constructor = constructor;
+    }
+
+    protected override void Initialize()
+    {
+        var disposeFunction = new ClrFunction(Engine, "disposeAsync", Dispose, 0, PropertyFlag.Configurable);
+
+        const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
+        var properties = new PropertyDictionary(8, checkExistingKeys: false)
+        {
+            ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable),
+            ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
+            ["adopt"] = new PropertyDescriptor(new ClrFunction(Engine, "adopt", Adopt, 2, PropertyFlag.Configurable), PropertyFlags),
+            ["defer"] = new PropertyDescriptor(new ClrFunction(Engine, "defer", Defer, 1, PropertyFlag.Configurable), PropertyFlags),
+            ["disposeAsync"] = new PropertyDescriptor(disposeFunction, PropertyFlags),
+            ["disposed"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get disposed", Disposed, 0, PropertyFlag.Configurable), set: Undefined, PropertyFlags),
+            ["move"] = new PropertyDescriptor(new ClrFunction(Engine, "move", Move, 0, PropertyFlag.Configurable), PropertyFlags),
+            ["use"] = new PropertyDescriptor(new ClrFunction(Engine, "use", Use, 1, PropertyFlag.Configurable), PropertyFlags),
+        };
+        SetProperties(properties);
+
+        var symbols = new SymbolDictionary(2)
+        {
+            [GlobalSymbolRegistry.AsyncDispose] = new PropertyDescriptor(disposeFunction, PropertyFlags),
+            [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("AsyncDisposableStack", PropertyFlag.Configurable),
+        };
+        SetSymbols(symbols);
+    }
+
+    private JsValue Adopt(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Adopt(arguments.At(0), arguments.At(1));
+    }
+
+    private JsValue Defer(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        stack.Defer(arguments.At(0));
+        return Undefined;
+    }
+
+    private JsValue Dispose(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Dispose();
+    }
+
+    private JsValue Disposed(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.State == DisposableState.Disposed;
+    }
+
+    private JsValue Move(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        var newDisposableStack = _engine.Intrinsics.Function.OrdinaryCreateFromConstructor(
+            _engine.Intrinsics.AsyncDisposableStack,
+            static intrinsics => intrinsics.AsyncDisposableStack.PrototypeObject,
+            static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Async));
+
+        return stack.Move(newDisposableStack);
+    }
+
+    private JsValue Use(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Use(arguments.At(0));
+    }
+
+    private DisposableStack AssertDisposableStack(JsValue thisObject)
+    {
+        if (thisObject is not DisposableStack { _hint: DisposeHint.Async } stack)
+        {
+            ExceptionHelper.ThrowTypeError(_engine.Realm, "This is not a AsyncDisposableStack instance.");
+            return null!;
+        }
+
+        return stack;
+    }
+}

+ 95 - 0
Jint/Native/Disposable/DisposableStack.cs

@@ -0,0 +1,95 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.Disposable;
+
+internal enum DisposableState
+{
+    Pending,
+    Disposed,
+}
+
+internal sealed class DisposableStack : ObjectInstance
+{
+    internal readonly DisposeHint _hint;
+    private DisposeCapability _disposeCapability;
+
+    public DisposableStack(Engine engine, DisposeHint hint) : base(engine)
+    {
+        _hint = hint;
+        State = DisposableState.Pending;
+        _disposeCapability = new DisposeCapability(engine);
+    }
+
+    public DisposableState State { get; private set; }
+
+    public JsValue Dispose()
+    {
+        if (State == DisposableState.Disposed)
+        {
+            return Undefined;
+        }
+
+        State = DisposableState.Disposed;
+        var completion = _disposeCapability.DisposeResources(new Completion(CompletionType.Normal, Undefined, _engine.GetLastSyntaxElement()));
+        if (completion.Type == CompletionType.Throw)
+        {
+            ExceptionHelper.ThrowJavaScriptException(_engine, completion.Value, completion);
+        }
+        return completion.Value;
+    }
+
+    public void Defer(JsValue onDispose)
+    {
+        AddDisposableResource(Undefined, _hint, onDispose.GetCallable(_engine.Realm));
+    }
+
+    public JsValue Use(JsValue value)
+    {
+        AddDisposableResource(value, _hint);
+        return value;
+    }
+
+    public JsValue Adopt(JsValue value, JsValue onDispose)
+    {
+        AssertNotDisposed();
+
+        var callable = onDispose.GetCallable(_engine.Realm);
+        JsCallDelegate closure = (_, _) =>
+        {
+            callable.Call(Undefined, value);
+            return Undefined;
+        };
+
+        var f = new ClrFunction(_engine, string.Empty, closure);
+        AddDisposableResource(Undefined, DisposeHint.Sync, f);
+
+        return value;
+    }
+
+    public JsValue Move(DisposableStack newDisposableStack)
+    {
+        AssertNotDisposed();
+
+        newDisposableStack.State = DisposableState.Pending;
+        newDisposableStack._disposeCapability = this._disposeCapability;
+        this._disposeCapability = new DisposeCapability(_engine);
+        State = DisposableState.Disposed;
+        return newDisposableStack;
+    }
+
+    private void AddDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
+    {
+        AssertNotDisposed();
+        _disposeCapability.AddDisposableResource(v, hint, method);
+    }
+
+    private void AssertNotDisposed()
+    {
+        if (State == DisposableState.Disposed)
+        {
+            ExceptionHelper.ThrowReferenceError(_engine.Realm, "Stack already disposed.");
+        }
+    }
+}

+ 34 - 0
Jint/Native/Disposable/DisposableStackConstructor.cs

@@ -0,0 +1,34 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.Disposable;
+
+internal sealed class DisposableStackConstructor : Constructor
+{
+    private static readonly JsString _name = new("DisposableStack");
+
+    public DisposableStackConstructor(Engine engine, Realm realm) : base(engine, realm, _name)
+    {
+        PrototypeObject = new DisposableStackPrototype(engine, realm, this, engine.Intrinsics.Object.PrototypeObject);
+        _length = new PropertyDescriptor(0, PropertyFlag.Configurable);
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+    }
+
+    internal DisposableStackPrototype PrototypeObject { get; }
+
+    public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
+    {
+        if (newTarget.IsUndefined())
+        {
+            ExceptionHelper.ThrowTypeError(_realm);
+        }
+
+        var stack = OrdinaryCreateFromConstructor(
+            newTarget,
+            static intrinsics => intrinsics.DisposableStack.PrototypeObject,
+            static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Sync));
+
+        return stack;
+    }
+}

+ 102 - 0
Jint/Native/Disposable/DisposableStackPrototype.cs

@@ -0,0 +1,102 @@
+using Jint.Native.Object;
+using Jint.Native.Symbol;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.Disposable;
+
+internal sealed class DisposableStackPrototype : Prototype
+{
+    private readonly DisposableStackConstructor _constructor;
+
+    internal DisposableStackPrototype(
+        Engine engine,
+        Realm realm,
+        DisposableStackConstructor constructor,
+        ObjectPrototype objectPrototype) : base(engine, realm)
+    {
+        _prototype = objectPrototype;
+        _constructor = constructor;
+    }
+
+    protected override void Initialize()
+    {
+        var disposeFunction = new ClrFunction(Engine, "dispose", Dispose, 0, PropertyFlag.Configurable);
+
+        const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
+        var properties = new PropertyDictionary(8, checkExistingKeys: false)
+        {
+            ["length"] = new PropertyDescriptor(0, PropertyFlag.Configurable),
+            ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
+            ["adopt"] = new PropertyDescriptor(new ClrFunction(Engine, "adopt", Adopt, 2, PropertyFlag.Configurable), PropertyFlags),
+            ["defer"] = new PropertyDescriptor(new ClrFunction(Engine, "defer", Defer, 1, PropertyFlag.Configurable), PropertyFlags),
+            ["dispose"] = new PropertyDescriptor(disposeFunction, PropertyFlags),
+            ["disposed"] = new GetSetPropertyDescriptor(get: new ClrFunction(Engine, "get disposed", Disposed, 0, PropertyFlag.Configurable), set: Undefined, PropertyFlags),
+            ["move"] = new PropertyDescriptor(new ClrFunction(Engine, "move", Move, 0, PropertyFlag.Configurable), PropertyFlags),
+            ["use"] = new PropertyDescriptor(new ClrFunction(Engine, "use", Use, 1, PropertyFlag.Configurable), PropertyFlags),
+        };
+        SetProperties(properties);
+
+        var symbols = new SymbolDictionary(2)
+        {
+            [GlobalSymbolRegistry.Dispose] = new PropertyDescriptor(disposeFunction, PropertyFlags),
+            [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("DisposableStack", PropertyFlag.Configurable),
+        };
+        SetSymbols(symbols);
+    }
+
+
+    private JsValue Adopt(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Adopt(arguments.At(0), arguments.At(1));
+    }
+
+    private JsValue Defer(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        stack.Defer(arguments.At(0));
+        return Undefined;
+    }
+
+    private JsValue Dispose(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Dispose();
+    }
+
+    private JsValue Disposed(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.State == DisposableState.Disposed;
+    }
+
+    private JsValue Move(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        var newDisposableStack = _engine.Intrinsics.Function.OrdinaryCreateFromConstructor(
+            _engine.Intrinsics.DisposableStack,
+            static intrinsics => intrinsics.DisposableStack.PrototypeObject,
+            static (Engine engine, Realm _, object? _) => new DisposableStack(engine, DisposeHint.Sync));
+
+        return stack.Move(newDisposableStack);
+    }
+
+    private JsValue Use(JsValue thisObject, JsCallArguments arguments)
+    {
+        var stack = AssertDisposableStack(thisObject);
+        return stack.Use(arguments.At(0));
+    }
+
+    private DisposableStack AssertDisposableStack(JsValue thisObject)
+    {
+        if (thisObject is not DisposableStack { _hint: DisposeHint.Sync } stack)
+        {
+            ExceptionHelper.ThrowTypeError(_engine.Realm, "This is not a DisposableStack instance.");
+            return null!;
+        }
+
+        return stack;
+    }
+}

+ 141 - 0
Jint/Native/Disposable/DisposeCapability.cs

@@ -0,0 +1,141 @@
+using Jint.Runtime;
+
+namespace Jint.Native.Disposable;
+
+internal enum DisposeHint
+{
+    Normal,
+    Sync,
+    Async,
+}
+
+internal sealed class DisposeCapability
+{
+    private readonly Engine _engine;
+    private readonly List<DisposableResource> _disposableResourceStack = [];
+
+    public DisposeCapability(Engine engine)
+    {
+        _engine = engine;
+    }
+
+    public void AddDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
+    {
+        DisposableResource resource;
+        if (method is null)
+        {
+            if (v.IsNullOrUndefined() && hint == DisposeHint.Sync)
+            {
+                return;
+            }
+
+            resource = CreateDisposableResource(v, hint);
+        }
+        else
+        {
+            resource = CreateDisposableResource(JsValue.Undefined, hint, method);
+        }
+
+        _disposableResourceStack.Add(resource);
+    }
+
+    private DisposableResource CreateDisposableResource(JsValue v, DisposeHint hint, ICallable? method = null)
+    {
+        if (method is null)
+        {
+            if (v.IsNullOrUndefined())
+            {
+                v = JsValue.Undefined;
+                method = null;
+            }
+            else
+            {
+                if (!v.IsObject())
+                {
+                    ExceptionHelper.ThrowTypeError(_engine.Realm, "Expected an object for disposable resource.");
+                    return default;
+                }
+                method = v.AsObject().GetDisposeMethod(hint);
+                if (method is null)
+                {
+                    ExceptionHelper.ThrowTypeError(_engine.Realm, "No dispose method found for the resource.");
+                    return default;
+                }
+            }
+        }
+
+        return new DisposableResource(v, hint, method);
+    }
+
+    public Completion DisposeResources(Completion c)
+    {
+        var needsAwait = false;
+        var hasAwaited = false;
+
+        for (var i = _disposableResourceStack.Count - 1; i >= 0; i--)
+        {
+            var (value, hint, method) = _disposableResourceStack[i];
+
+            if (hint == DisposeHint.Sync && needsAwait && !hasAwaited)
+            {
+                _engine.RunAvailableContinuations();
+                needsAwait = false;
+            }
+
+            if (method is not null)
+            {
+                var result = JsValue.Undefined;
+                JavaScriptException? exception = null;
+                try
+                {
+                    result = method.Call(value);
+                    if (hint == DisposeHint.Async)
+                    {
+                        hasAwaited = true;
+                        try
+                        {
+                            result = result.UnwrapIfPromise();
+                        }
+                        catch (JavaScriptException e)
+                        {
+                            exception = e;
+                        }
+                    }
+                }
+                catch (JavaScriptException e)
+                {
+                    exception = e;
+                }
+
+                if (exception is not null)
+                {
+                    if (c.Type == CompletionType.Throw)
+                    {
+                        var error = _engine.Intrinsics.SuppressedError.Construct(_engine.Intrinsics.SuppressedError, "", exception.Error, c.Value);
+                        c = new Completion(CompletionType.Throw, error, c._source);
+                    }
+                    else
+                    {
+                        c = new Completion(CompletionType.Throw, exception.Error, c._source);
+                    }
+                }
+            }
+            else
+            {
+                // Assert: hint is "async-dispose"
+                needsAwait = true;
+            }
+        }
+
+        if (needsAwait && !hasAwaited)
+        {
+            _engine.RunAvailableContinuations();
+        }
+
+        _disposableResourceStack.Clear();
+        return c;
+    }
+
+    private readonly record struct DisposableResource(JsValue ResourceValue, DisposeHint Hint, ICallable? DisposeMethod);
+}
+

+ 7 - 1
Jint/Native/Global/GlobalObject.Properties.cs

@@ -10,6 +10,7 @@ public partial class GlobalObject
     private static readonly Key propertyAggregateError = "AggregateError";
     private static readonly Key propertyArray = "Array";
     private static readonly Key propertyArrayBuffer = "ArrayBuffer";
+    private static readonly Key propertyAsyncDisposableStack = "AsyncDisposableStack";
     private static readonly Key propertyAtomics = "Atomics";
     private static readonly Key propertyBigInt = "BigInt";
     private static readonly Key propertyBigInt64Array = "BigInt64Array";
@@ -17,6 +18,7 @@ public partial class GlobalObject
     private static readonly Key propertyBoolean = "Boolean";
     private static readonly Key propertyDataView = "DataView";
     private static readonly Key propertyDate = "Date";
+    private static readonly Key propertyDisposableStack = "DisposableStack";
     private static readonly Key propertyError = "Error";
     private static readonly Key propertyEvalError = "EvalError";
     private static readonly Key propertyFinalizationRegistry = "FinalizationRegistry";
@@ -46,6 +48,7 @@ public partial class GlobalObject
     private static readonly Key propertyString = "String";
     private static readonly Key propertySymbol = "Symbol";
     private static readonly Key propertySyntaxError = "SyntaxError";
+    private static readonly Key propertySuppressedError = "SuppressedError";
     private static readonly Key propertyTypeError = "TypeError";
     private static readonly Key propertyTypedArray = "TypedArray";
     private static readonly Key propertyURIError = "URIError";
@@ -82,10 +85,11 @@ public partial class GlobalObject
         const PropertyFlag LengthFlags = PropertyFlag.Configurable;
         const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
 
-        var properties = new StringDictionarySlim<PropertyDescriptor>(65);
+        var properties = new StringDictionarySlim<PropertyDescriptor>(70);
         properties.AddDangerous(propertyAggregateError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.AggregateError, PropertyFlags));
         properties.AddDangerous(propertyArray, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Array, PropertyFlags));
         properties.AddDangerous(propertyArrayBuffer, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.ArrayBuffer, PropertyFlags));
+        properties.AddDangerous(propertyAsyncDisposableStack, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.AsyncDisposableStack, PropertyFlags));
         properties.AddDangerous(propertyAtomics, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Atomics, PropertyFlags));
         properties.AddDangerous(propertyBigInt, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.BigInt, PropertyFlags));
         properties.AddDangerous(propertyBigInt64Array, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.BigInt64Array, PropertyFlags));
@@ -93,6 +97,7 @@ public partial class GlobalObject
         properties.AddDangerous(propertyBoolean, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Boolean, PropertyFlags));
         properties.AddDangerous(propertyDataView, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.DataView, PropertyFlags));
         properties.AddDangerous(propertyDate, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Date, PropertyFlags));
+        properties.AddDangerous(propertyDisposableStack, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.DisposableStack, PropertyFlags));
         properties.AddDangerous(propertyError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Error, PropertyFlags));
         properties.AddDangerous(propertyEvalError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.EvalError, PropertyFlags));
         properties.AddDangerous(propertyFinalizationRegistry, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.FinalizationRegistry, PropertyFlags));
@@ -122,6 +127,7 @@ public partial class GlobalObject
         properties.AddDangerous(propertyString, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.String, PropertyFlags));
         properties.AddDangerous(propertySymbol, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.Symbol, PropertyFlags));
         properties.AddDangerous(propertySyntaxError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.SyntaxError, PropertyFlags));
+        properties.AddDangerous(propertySuppressedError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.SuppressedError, PropertyFlags));
         properties.AddDangerous(propertyTypeError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.TypeError, PropertyFlags));
         properties.AddDangerous(propertyTypedArray, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.TypedArray, PropertyFlags));
         properties.AddDangerous(propertyURIError, new LazyPropertyDescriptor<GlobalObject>(this, static global => global._realm.Intrinsics.UriError, PropertyFlags));

+ 14 - 2
Jint/Native/Iterator/IteratorPrototype.cs

@@ -20,9 +20,10 @@ internal class IteratorPrototype : Prototype
 
     protected override void Initialize()
     {
-        var symbols = new SymbolDictionary(1)
+        var symbols = new SymbolDictionary(2)
         {
-            [GlobalSymbolRegistry.Iterator] = new(new ClrFunction(Engine, "[Symbol.iterator]", ToIterator, 0, PropertyFlag.Configurable), true, false, true),
+            [GlobalSymbolRegistry.Iterator] = new(new ClrFunction(Engine, "[Symbol.iterator]", ToIterator, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
+            [GlobalSymbolRegistry.Dispose] = new(new ClrFunction(Engine, "[Symbol.dispose]", Dispose, 0, PropertyFlag.Configurable), PropertyFlag.NonEnumerable),
         };
         SetSymbols(symbols);
     }
@@ -32,6 +33,17 @@ internal class IteratorPrototype : Prototype
         return thisObject;
     }
 
+    private static JsValue Dispose(JsValue thisObject, JsCallArguments arguments)
+    {
+        var method = thisObject.AsObject().GetMethod(CommonProperties.Return);
+        if (method is not null)
+        {
+            method.Call(thisObject, arguments);
+        }
+
+        return Undefined;
+    }
+
     internal JsValue Next(JsValue thisObject, JsCallArguments arguments)
     {
         var iterator = thisObject as IteratorInstance;

+ 38 - 0
Jint/Native/Object/ObjectInstance.cs

@@ -5,8 +5,10 @@ using Jint.Collections;
 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;
 using Jint.Native.String;
 using Jint.Native.Symbol;
 using Jint.Native.TypedArray;
@@ -1396,6 +1398,42 @@ public partial class ObjectInstance : JsValue, IEquatable<ObjectInstance>
         return callable;
     }
 
+    internal ICallable? GetDisposeMethod(DisposeHint hint)
+    {
+        if (hint == DisposeHint.Async)
+        {
+            var method = GetMethod(GlobalSymbolRegistry.AsyncDispose);
+            if (method is null)
+            {
+                method = GetMethod(GlobalSymbolRegistry.Dispose);
+                if (method is not null)
+                {
+                    JsCallDelegate closure = (_, _) =>
+                    {
+                        var promiseCapability = PromiseConstructor.NewPromiseCapability(_engine, _engine.Intrinsics.Promise);
+                        try
+                        {
+                            method.Call(this);
+                            promiseCapability.Resolve.Call(Undefined, Undefined);
+                        }
+                        catch
+                        {
+                            promiseCapability.Reject.Call(Undefined, Undefined);
+                        }
+                        return promiseCapability.PromiseInstance;
+                    };
+
+                    return new ClrFunction(_engine, string.Empty, closure);
+                }
+            }
+
+            return method;
+        }
+
+        return GetMethod(GlobalSymbolRegistry.Dispose);
+    }
+
+
     internal void CopyDataProperties(
         ObjectInstance target,
         HashSet<JsValue>? excludedItems)

+ 61 - 0
Jint/Native/SuppressedError/SuppressedErrorConstructor.cs

@@ -0,0 +1,61 @@
+using Jint.Native.Error;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.SuppressedError;
+
+internal sealed class SuppressedErrorConstructor : Constructor
+{
+    private static readonly JsString _name = new("SuppressedError");
+
+    internal SuppressedErrorConstructor(
+        Engine engine,
+        Realm realm,
+        ErrorConstructor errorConstructor)
+        : base(engine, realm, _name)
+    {
+        _prototype = errorConstructor;
+        PrototypeObject = new SuppressedErrorPrototype(engine, realm, this, errorConstructor.PrototypeObject);
+        _length = new PropertyDescriptor(JsNumber.PositiveThree, PropertyFlag.Configurable);
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+    }
+
+    private SuppressedErrorPrototype PrototypeObject { get; }
+
+    protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments)
+    {
+        return Construct(arguments, this);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-nativeerror
+    /// </summary>
+    public override ObjectInstance Construct(JsCallArguments arguments, JsValue newTarget)
+    {
+        var error = arguments.At(0);
+        var suppressed = arguments.At(1);
+        var message = arguments.At(2);
+
+        return Construct(newTarget, message, error, suppressed);
+    }
+
+    internal JsError Construct(JsValue newTarget, JsValue message, JsValue error, JsValue suppressed)
+    {
+        var o = OrdinaryCreateFromConstructor(
+            newTarget,
+            static intrinsics => intrinsics.SuppressedError.PrototypeObject,
+            static (Engine engine, Realm _, object? _) => new JsError(engine));
+
+        if (!message.IsUndefined())
+        {
+            var msg = TypeConverter.ToString(message);
+            o.CreateNonEnumerableDataPropertyOrThrow(CommonProperties.Message, msg);
+        }
+
+        o.DefinePropertyOrThrow("error", new PropertyDescriptor(error, configurable: true, enumerable: false, writable: true));
+        o.DefinePropertyOrThrow("suppressed", new PropertyDescriptor(suppressed, configurable: true, enumerable: false, writable: true));
+
+        return o;
+    }
+}

+ 32 - 0
Jint/Native/SuppressedError/SuppressedErrorPrototype.cs

@@ -0,0 +1,32 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.SuppressedError;
+
+internal sealed class SuppressedErrorPrototype : Prototype
+{
+    private readonly SuppressedErrorConstructor _constructor;
+
+    internal SuppressedErrorPrototype(
+        Engine engine,
+        Realm realm,
+        SuppressedErrorConstructor constructor,
+        ObjectInstance prototype)
+        : base(engine, realm)
+    {
+        _constructor = constructor;
+        _prototype = prototype;
+    }
+
+    protected override void Initialize()
+    {
+        var properties = new PropertyDictionary(3, checkExistingKeys: false)
+        {
+            ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
+            ["message"] = new PropertyDescriptor(JsString.Empty, PropertyFlag.Configurable | PropertyFlag.Writable),
+            ["name"] = new PropertyDescriptor("SuppressedError", PropertyFlag.Configurable | PropertyFlag.Writable),
+        };
+        SetProperties(properties);
+    }
+}

+ 15 - 13
Jint/Native/Symbol/GlobalSymbolRegistry.cs

@@ -4,19 +4,21 @@ namespace Jint.Native.Symbol;
 
 public sealed class GlobalSymbolRegistry
 {
-    public static readonly JsSymbol AsyncIterator = new JsSymbol("Symbol.asyncIterator");
-    public static readonly JsSymbol HasInstance = new JsSymbol("Symbol.hasInstance");
-    public static readonly JsSymbol IsConcatSpreadable = new JsSymbol("Symbol.isConcatSpreadable");
-    public static readonly JsSymbol Iterator = new JsSymbol("Symbol.iterator");
-    public static readonly JsSymbol Match = new JsSymbol("Symbol.match");
-    public static readonly JsSymbol MatchAll = new JsSymbol("Symbol.matchAll");
-    public static readonly JsSymbol Replace = new JsSymbol("Symbol.replace");
-    public static readonly JsSymbol Search = new JsSymbol("Symbol.search");
-    public static readonly JsSymbol Species = new JsSymbol("Symbol.species");
-    public static readonly JsSymbol Split = new JsSymbol("Symbol.split");
-    public static readonly JsSymbol ToPrimitive = new JsSymbol("Symbol.toPrimitive");
-    public static readonly JsSymbol ToStringTag = new JsSymbol("Symbol.toStringTag");
-    public static readonly JsSymbol Unscopables = new JsSymbol("Symbol.unscopables");
+    public static readonly JsSymbol AsyncDispose = new("Symbol.asyncDispose");
+    public static readonly JsSymbol AsyncIterator = new("Symbol.asyncIterator");
+    public static readonly JsSymbol Dispose = new("Symbol.dispose");
+    public static readonly JsSymbol HasInstance = new("Symbol.hasInstance");
+    public static readonly JsSymbol IsConcatSpreadable = new("Symbol.isConcatSpreadable");
+    public static readonly JsSymbol Iterator = new("Symbol.iterator");
+    public static readonly JsSymbol Match = new("Symbol.match");
+    public static readonly JsSymbol MatchAll = new("Symbol.matchAll");
+    public static readonly JsSymbol Replace = new("Symbol.replace");
+    public static readonly JsSymbol Search = new("Symbol.search");
+    public static readonly JsSymbol Species = new("Symbol.species");
+    public static readonly JsSymbol Split = new("Symbol.split");
+    public static readonly JsSymbol ToPrimitive = new("Symbol.toPrimitive");
+    public static readonly JsSymbol ToStringTag = new("Symbol.toStringTag");
+    public static readonly JsSymbol Unscopables = new("Symbol.unscopables");
 
     // engine-specific created by scripts
     private Dictionary<JsValue, JsSymbol>? _customSymbolLookup;

+ 4 - 2
Jint/Native/Symbol/SymbolConstructor.cs

@@ -36,7 +36,7 @@ internal sealed class SymbolConstructor : Constructor
         const PropertyFlag lengthFlags = PropertyFlag.Configurable;
         const PropertyFlag propertyFlags = PropertyFlag.AllForbidden;
 
-        var properties = new PropertyDictionary(15, checkExistingKeys: false)
+        var properties = new PropertyDictionary(17, checkExistingKeys: false)
         {
             ["for"] = new PropertyDescriptor(new ClrFunction(Engine, "for", For, 1, lengthFlags), PropertyFlag.Writable | PropertyFlag.Configurable),
             ["keyFor"] = new PropertyDescriptor(new ClrFunction(Engine, "keyFor", KeyFor, 1, lengthFlags), PropertyFlag.Writable | PropertyFlag.Configurable),
@@ -52,7 +52,9 @@ internal sealed class SymbolConstructor : Constructor
             ["toPrimitive"] = new PropertyDescriptor(GlobalSymbolRegistry.ToPrimitive, propertyFlags),
             ["toStringTag"] = new PropertyDescriptor(GlobalSymbolRegistry.ToStringTag, propertyFlags),
             ["unscopables"] = new PropertyDescriptor(GlobalSymbolRegistry.Unscopables, propertyFlags),
-            ["asyncIterator"] = new PropertyDescriptor(GlobalSymbolRegistry.AsyncIterator, propertyFlags)
+            ["asyncIterator"] = new PropertyDescriptor(GlobalSymbolRegistry.AsyncIterator, propertyFlags),
+            ["dispose"] = new PropertyDescriptor(GlobalSymbolRegistry.Dispose, propertyFlags),
+            ["asyncDispose"] = new PropertyDescriptor(GlobalSymbolRegistry.AsyncDispose, propertyFlags),
         };
         SetProperties(properties);
     }

+ 15 - 0
Jint/Runtime/Intrinsics.cs

@@ -8,6 +8,7 @@ using Jint.Native.BigInt;
 using Jint.Native.Boolean;
 using Jint.Native.DataView;
 using Jint.Native.Date;
+using Jint.Native.Disposable;
 using Jint.Native.Error;
 using Jint.Native.FinalizationRegistry;
 using Jint.Native.Function;
@@ -26,6 +27,7 @@ using Jint.Native.Set;
 using Jint.Native.ShadowRealm;
 using Jint.Native.SharedArrayBuffer;
 using Jint.Native.String;
+using Jint.Native.SuppressedError;
 using Jint.Native.Symbol;
 using Jint.Native.TypedArray;
 using Jint.Native.WeakMap;
@@ -50,6 +52,7 @@ public sealed partial class Intrinsics
     // lazy properties
     private ThrowTypeError? _throwTypeError;
     private AggregateErrorConstructor? _aggregateError;
+    private SuppressedErrorConstructor? _suppressedError;
     private ErrorConstructor? _error;
     private ErrorConstructor? _evalError;
     private ErrorConstructor? _rangeError;
@@ -107,6 +110,9 @@ public sealed partial class Intrinsics
 
     private ShadowRealmConstructor? _shadowRealm;
 
+    private AsyncDisposableStackConstructor? _asyncDisposableStack;
+    private DisposableStackConstructor? _disposableStack;
+
     internal Intrinsics(Engine engine, Realm realm)
     {
         _engine = engine;
@@ -141,6 +147,9 @@ public sealed partial class Intrinsics
     internal AggregateErrorConstructor AggregateError =>
         _aggregateError ??= new AggregateErrorConstructor(_engine, _realm, Error);
 
+    internal SuppressedErrorConstructor SuppressedError =>
+        _suppressedError ??= new SuppressedErrorConstructor(_engine, _realm, Error);
+
     internal ArrayIteratorPrototype ArrayIteratorPrototype =>
         _arrayIteratorPrototype ??= new ArrayIteratorPrototype(_engine, _realm, this.IteratorPrototype);
 
@@ -293,4 +302,10 @@ public sealed partial class Intrinsics
 
     internal ThrowTypeError ThrowTypeError =>
         _throwTypeError ??= new ThrowTypeError(_engine, _engine.Realm) { _prototype = _engine.Realm.Intrinsics.Function.PrototypeObject };
+
+    internal AsyncDisposableStackConstructor AsyncDisposableStack =>
+        _asyncDisposableStack ??= new AsyncDisposableStackConstructor(_engine, _engine.Realm) { _prototype = _engine.Realm.Intrinsics.Function.PrototypeObject };
+
+    internal DisposableStackConstructor DisposableStack =>
+        _disposableStack ??= new DisposableStackConstructor(_engine, _engine.Realm) { _prototype = _engine.Realm.Intrinsics.Function.PrototypeObject };
 }