Explorar o código

Implement FinalizationRegistry (#1328)

Marko Lahma %!s(int64=2) %!d(string=hai) anos
pai
achega
f862659980

+ 0 - 2
Jint.Tests.Test262/Test262Harness.settings.json

@@ -15,8 +15,6 @@
     "class-static-fields-public",
     "class-static-methods-private",
     "decorators",
-    "FinalizationRegistry",
-    "FinalizationRegistry.prototype.cleanupSome",
     "generators",
     "import-assertions",
     "regexp-duplicate-named-groups",

+ 1 - 0
Jint.Tests.Test262/Test262Test.cs

@@ -65,6 +65,7 @@ public abstract partial class Test262Test
             (_, _) =>
             {
                 GC.Collect();
+                GC.WaitForPendingFinalizers();
                 return JsValue.Undefined;
             }), true, true, true));
 

+ 59 - 0
Jint/Native/FinalizationRegistry/FinalizationRegistryConstructor.cs

@@ -0,0 +1,59 @@
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.FinalizationRegistry;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-finalization-registry-constructor
+/// </summary>
+internal sealed class FinalizationRegistryConstructor : FunctionInstance, IConstructor
+{
+    private static readonly JsString _functionName = new("FinalizationRegistry");
+
+    public FinalizationRegistryConstructor(
+        Engine engine,
+        Realm realm,
+        FunctionConstructor functionConstructor,
+        ObjectPrototype objectPrototype) : base(engine, realm, _functionName)
+    {
+        PrototypeObject = new FinalizationRegistryPrototype(engine, realm, this, objectPrototype);
+        _prototype = functionConstructor.PrototypeObject;
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+        _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable);
+    }
+
+    public FinalizationRegistryPrototype PrototypeObject { get; }
+
+    protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
+    {
+        return Construct(arguments, thisObject);
+    }
+
+    ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);
+
+    private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
+    {
+        if (newTarget.IsUndefined())
+        {
+            ExceptionHelper.ThrowTypeError(_realm);
+        }
+
+        var cleanupCallback = arguments.At(0);
+        if (cleanupCallback is not ICallable callable)
+        {
+            ExceptionHelper.ThrowTypeError(_realm, "cleanup must be callable");
+            return null;
+        }
+
+        var finalizationRegistry = OrdinaryCreateFromConstructor(
+            newTarget,
+            static intrinsics => intrinsics.FinalizationRegistry.PrototypeObject,
+            (engine, realm, state) => new FinalizationRegistryInstance(engine, realm, state!),
+            callable
+        );
+
+        return finalizationRegistry;
+    }
+}

+ 68 - 0
Jint/Native/FinalizationRegistry/FinalizationRegistryInstance.cs

@@ -0,0 +1,68 @@
+using System.Runtime.CompilerServices;
+using Jint.Native.Object;
+using Jint.Runtime;
+
+namespace Jint.Native.FinalizationRegistry;
+
+internal sealed record Cell(JsValue WeakRefTarget, JsValue HeldValue, ObjectInstance? UnregisterToken);
+
+internal sealed class FinalizationRegistryInstance : ObjectInstance
+{
+    private readonly Realm _realm;
+    private readonly JobCallback _callable;
+    private readonly ConditionalWeakTable<JsValue, List<Observer>> _cells = new();
+    private readonly Dictionary<JsValue, List<Observer>> _byToken = new();
+
+    public FinalizationRegistryInstance(Engine engine, Realm realm, ICallable cleanupCallback) : base(engine)
+    {
+        _realm = realm;
+        _callable = engine._host.MakeJobCallBack(cleanupCallback);
+    }
+
+    public void CleanupFinalizationRegistry(ICallable? callback)
+    {
+    }
+
+    public void AddCell(Cell cell)
+    {
+        var observer = new Observer(_callable);
+        var observerList = _cells.GetOrCreateValue(cell.WeakRefTarget);
+        observerList.Add(observer);
+
+        if (cell.UnregisterToken is not null)
+        {
+            if (!_byToken.TryGetValue(cell.UnregisterToken, out var list))
+            {
+                _byToken[cell.UnregisterToken] = list = new List<Observer>();
+            }
+            list.Add(observer);
+        }
+    }
+
+    public JsValue Remove(JsValue unregisterToken)
+    {
+        if (_byToken.TryGetValue(unregisterToken, out var list))
+        {
+            var any = list.Count > 0;
+            list.Clear();
+            return any;
+        }
+
+        return false;
+    }
+
+    private sealed class Observer
+    {
+        private readonly JobCallback _callable;
+
+        public Observer(JobCallback callable)
+        {
+            _callable = callable;
+        }
+
+        ~Observer()
+        {
+            _callable.Callback.Call(Undefined);
+        }
+    }
+}

+ 120 - 0
Jint/Native/FinalizationRegistry/FinalizationRegistryPrototype.cs

@@ -0,0 +1,120 @@
+using Jint.Collections;
+using Jint.Native.Object;
+using Jint.Native.Symbol;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.FinalizationRegistry;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-properties-of-the-finalization-registry-prototype-object
+/// </summary>
+internal sealed class FinalizationRegistryPrototype : Prototype
+{
+    private readonly FinalizationRegistryConstructor _constructor;
+
+    public FinalizationRegistryPrototype(
+        Engine engine,
+        Realm realm,
+        FinalizationRegistryConstructor constructor,
+        ObjectPrototype objectPrototype) : base(engine, realm)
+    {
+        _constructor = constructor;
+        _prototype = objectPrototype;
+    }
+
+    protected override void Initialize()
+    {
+        const PropertyFlag PropertyFlags = PropertyFlag.NonEnumerable;
+        var properties = new PropertyDictionary(4, checkExistingKeys: false)
+        {
+            [KnownKeys.Constructor] = new(_constructor, PropertyFlag.NonEnumerable),
+            ["register"] = new(new ClrFunctionInstance(Engine, "register", Register, 2, PropertyFlag.Configurable), PropertyFlags),
+            ["unregister"] = new(new ClrFunctionInstance(Engine, "unregister", Unregister, 1, PropertyFlag.Configurable), PropertyFlags),
+            ["cleanupSome"] = new(new ClrFunctionInstance(Engine, "cleanupSome", CleanupSome, 0, PropertyFlag.Configurable), PropertyFlags),
+        };
+        SetProperties(properties);
+
+        var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.ToStringTag] = new("FinalizationRegistry", PropertyFlag.Configurable) };
+        SetSymbols(symbols);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-finalization-registry.prototype.register
+    /// </summary>
+    private JsValue Register(JsValue thisObj, JsValue[] arguments)
+    {
+        var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);
+
+        var target = arguments.At(0);
+        var heldValue = arguments.At(1);
+        var unregisterToken = arguments.At(2);
+
+        if (target is not ObjectInstance)
+        {
+            ExceptionHelper.ThrowTypeError(_realm, "target must be an object");
+        }
+
+        if (SameValue(target, heldValue))
+        {
+            ExceptionHelper.ThrowTypeError(_realm, "target and holdings must not be same");
+        }
+
+        if (unregisterToken is not ObjectInstance oi)
+        {
+            if (!unregisterToken.IsUndefined())
+            {
+                ExceptionHelper.ThrowTypeError(_realm, unregisterToken + " must be an object");
+            }
+
+        }
+        var cell = new Cell(target, heldValue, unregisterToken as ObjectInstance);
+        finalizationRegistry.AddCell(cell);
+        return Undefined;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister
+    /// </summary>
+    private JsValue Unregister(JsValue thisObj, JsValue[] arguments)
+    {
+        var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);
+
+        var unregisterToken = arguments.At(0);
+
+        if (unregisterToken is not ObjectInstance oi)
+        {
+            ExceptionHelper.ThrowTypeError(_realm, unregisterToken + " must be an object");
+        }
+
+        return finalizationRegistry.Remove(unregisterToken);
+    }
+
+    private JsValue CleanupSome(JsValue thisObj, JsValue[] arguments)
+    {
+        var finalizationRegistry = AssertFinalizationRegistryInstance(thisObj);
+        var callback = arguments.At(0);
+
+        if (!callback.IsUndefined() && callback is not ICallable)
+        {
+            ExceptionHelper.ThrowTypeError(_realm, callback + " must be callable");
+        }
+
+        finalizationRegistry.CleanupFinalizationRegistry(callback as ICallable);
+
+        return Undefined;
+    }
+
+    private FinalizationRegistryInstance AssertFinalizationRegistryInstance(JsValue thisObj)
+    {
+        if (thisObj is not FinalizationRegistryInstance finalizationRegistryInstance)
+        {
+            ExceptionHelper.ThrowTypeError(_realm, "object must be a FinalizationRegistry");
+            return null;
+        }
+
+        return finalizationRegistryInstance;
+    }
+}
+

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

@@ -42,7 +42,7 @@ namespace Jint.Native.Global
                 ["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, propertyFlags),
                 ["Error"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, propertyFlags),
                 ["EvalError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, propertyFlags),
-                ["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags),
+                ["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, propertyFlags),
                 ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags),
                 ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags),
                 ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags),

+ 11 - 0
Jint/Runtime/Host.cs

@@ -1,4 +1,5 @@
 using Jint.Native;
+using Jint.Native.Function;
 using Jint.Native.Global;
 using Jint.Native.Object;
 using Jint.Native.Promise;
@@ -190,5 +191,15 @@ namespace Jint.Runtime
         public virtual void InitializeShadowRealm(Realm realm)
         {
         }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-hostmakejobcallback
+        /// </summary>
+        internal virtual JobCallback MakeJobCallBack(ICallable cleanupCallback)
+        {
+            return new JobCallback(cleanupCallback, null);
+        }
     }
 }
+
+internal sealed record JobCallback(ICallable Callback, object? HostDefined);

+ 5 - 0
Jint/Runtime/Intrinsics.cs

@@ -8,6 +8,7 @@ using Jint.Native.Boolean;
 using Jint.Native.DataView;
 using Jint.Native.Date;
 using Jint.Native.Error;
+using Jint.Native.FinalizationRegistry;
 using Jint.Native.Function;
 using Jint.Native.Iterator;
 using Jint.Native.Json;
@@ -81,6 +82,7 @@ namespace Jint.Runtime
         private ArrayBufferConstructor? _arrayBufferConstructor;
         private DataViewConstructor? _dataView;
         private AsyncFunctionConstructor? _asyncFunction;
+        private FinalizationRegistryConstructor? _finalizationRegistry;
 
         private IntrinsicTypedArrayConstructor? _typedArray;
         private Int8ArrayConstructor? _int8Array;
@@ -116,6 +118,9 @@ namespace Jint.Runtime
         public ObjectConstructor Object { get; }
         public FunctionConstructor Function { get; }
 
+        internal FinalizationRegistryConstructor FinalizationRegistry =>
+            _finalizationRegistry ??= new FinalizationRegistryConstructor(_engine, _realm, Function, Object.PrototypeObject);
+
         internal AsyncFunctionConstructor AsyncFunction =>
             _asyncFunction ??= new AsyncFunctionConstructor(_engine, _realm, Function);
 

+ 1 - 1
Jint/Runtime/JavaScriptException.cs

@@ -17,7 +17,7 @@ public class JavaScriptException : JintException
         }
         else if (error is not null)
         {
-            ret = TypeConverter.ToString(error);
+            ret = error.IsSymbol() ? error.ToString() : TypeConverter.ToString(error);
         }
 
         return ret;

+ 2 - 2
README.md

@@ -49,7 +49,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
 #### ECMAScript 2016
 
 - ✔ `Array.prototype.includes`
--  `await`, `async`
+-  `await`, `async`
 - ✔ Block-scoping of variables and functions
 - ✔ Exponentiation operator `**`
 - ✔ Destructuring patterns (of variables)
@@ -93,7 +93,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
 - ✔ `Promise.any` 
 - ✔ `String.prototype.replaceAll`
 - ✔ `WeakRef` 
--  `FinalizationRegistry`
+-  `FinalizationRegistry`
 
 #### ECMAScript 2022