Przeglądaj źródła

Allow deleting dictionary entries via interop (#1341)

Marko Lahma 2 lat temu
rodzic
commit
92fbdc3410

+ 10 - 0
Jint.Tests.PublicInterface/RavenApiUsageTests.cs

@@ -3,6 +3,7 @@ using Jint.Constraints;
 using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
 
 namespace Jint.Tests.PublicInterface;
 
@@ -68,4 +69,13 @@ public class RavenApiUsageTests
         Assert.Equal(1L, array.Length);
         Assert.Equal(42, array[0]);
     }
+
+    [Fact]
+    public void CanGetPropertyDescriptor()
+    {
+        var engine = new Engine();
+        var obj = new DirectoryInfo("the-path");
+        var propertyDescriptor = ObjectWrapper.GetPropertyDescriptor(engine, obj, obj.GetType().GetProperty(nameof(DirectoryInfo.Name)));
+        Assert.Equal("the-path", propertyDescriptor.Value);
+    }
 }

+ 48 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -3224,5 +3224,53 @@ try {
                 equal('bar', obj.Value.foo);
             ");
         }
+
+        [Fact]
+        public void ShouldBeAbleToDeleteDictionaryEntries()
+        {
+            var engine = new Engine(options => options.Strict());
+
+            var dictionary = new Dictionary<string, int>
+            {
+                { "a", 1 },
+                { "b", 2 }
+            };
+
+            engine.SetValue("data", dictionary);
+            
+            Assert.True(engine.Evaluate("Object.hasOwn(data, 'a')").AsBoolean());
+            Assert.True(engine.Evaluate("data['a'] === 1").AsBoolean());
+
+            engine.Evaluate("data['a'] = 42");
+            Assert.True(engine.Evaluate("data['a'] === 42").AsBoolean());
+            
+            Assert.Equal(42, dictionary["a"]);
+
+            engine.Execute("delete data['a'];");
+            
+            Assert.False(engine.Evaluate("Object.hasOwn(data, 'a')").AsBoolean());
+            Assert.False(engine.Evaluate("data['a'] === 42").AsBoolean());
+            
+            Assert.False(dictionary.ContainsKey("a"));
+            
+            var engineNoWrite = new Engine(options => options.Strict().AllowClrWrite(false));
+
+            dictionary = new Dictionary<string, int>
+            {
+                { "a", 1 },
+                { "b", 2 }
+            };
+            
+            engineNoWrite.SetValue("data", dictionary);
+            
+            var ex1 = Assert.Throws<JavaScriptException>(() => engineNoWrite.Evaluate("data['a'] = 42"));
+            Assert.Equal("Cannot assign to read only property 'a' of System.Collections.Generic.Dictionary`2[System.String,System.Int32]", ex1.Message);
+
+            // no changes
+            Assert.True(engineNoWrite.Evaluate("data['a'] === 1").AsBoolean());
+
+            var ex2 = Assert.Throws<JavaScriptException>(() => engineNoWrite.Execute("delete data['a'];"));
+            Assert.Equal("Cannot delete property 'a' of System.Collections.Generic.Dictionary`2[System.String,System.Int32]", ex2.Message);
+        }
     }
 }

+ 2 - 3
Jint/Engine.cs

@@ -580,13 +580,12 @@ namespace Jint
                 var succeeded = baseValue.Set(reference.GetReferencedName(), value, reference.GetThisValue());
                 if (!succeeded && reference.IsStrictReference())
                 {
-                    ExceptionHelper.ThrowTypeError(Realm);
+                    ExceptionHelper.ThrowTypeError(Realm, "Cannot assign to read only property '" + reference.GetReferencedName() + "' of " + baseValue);
                 }
             }
             else
             {
-                ((EnvironmentRecord) baseValue).SetMutableBinding(TypeConverter.ToString(reference.GetReferencedName()),
-                    value, reference.IsStrictReference());
+                ((EnvironmentRecord) baseValue).SetMutableBinding(TypeConverter.ToString(reference.GetReferencedName()), value, reference.IsStrictReference());
             }
         }
 

+ 1 - 1
Jint/Native/BigInt/BigIntPrototype.cs

@@ -22,7 +22,7 @@ public sealed class BigIntPrototype : ObjectInstance
         Realm realm,
         BigIntConstructor constructor,
         ObjectPrototype objectPrototype)
-        : base(engine, ObjectClass.Object, InternalTypes.BigInt)
+        : base(engine, ObjectClass.Object)
     {
         _prototype = objectPrototype;
         _realm = realm;

+ 9 - 0
Jint/Native/JsValue.cs

@@ -158,6 +158,15 @@ namespace Jint.Native
             return engine.Invoke(this, arguments);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-getv
+        /// </summary>
+        internal JsValue GetV(Realm realm, JsValue property)
+        {
+            var o = TypeConverter.ToObject(realm, this);
+            return o.Get(property, this);
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public JsValue Get(JsValue property)
         {

+ 1 - 1
Jint/Native/Json/JsonSerializer.cs

@@ -137,7 +137,7 @@ namespace Jint.Native.Json
             var isBigInt = value is BigIntInstance || value.IsBigInt();
             if (value.IsObject() || isBigInt)
             {
-                var toJson = value.Get(toJsonProperty, value);
+                var toJson = value.GetV(_engine.Realm, toJsonProperty);
                 if (toJson.IsUndefined() && isBigInt)
                 {
                     toJson = _engine.Realm.Intrinsics.BigInt.PrototypeObject.Get(toJsonProperty);

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

@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using Jint.Collections;
 using Jint.Native.Array;
+using Jint.Native.BigInt;
 using Jint.Native.Boolean;
 using Jint.Native.Date;
 using Jint.Native.Function;
@@ -977,6 +978,12 @@ namespace Jint.Native.Object
                         break;
                     }
 
+                    if (this is BigIntInstance bigIntInstance)
+                    {
+                        converted = bigIntInstance.BigIntData._value;
+                        break;
+                    }
+
                     var o = _engine.Options.Interop.CreateClrObject(this);
                     foreach (var p in GetOwnProperties())
                     {

+ 14 - 1
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -98,6 +98,14 @@ namespace Jint.Runtime.Interop
             return Target;
         }
 
+        public override void RemoveOwnProperty(JsValue property)
+        {
+            if (_engine.Options.Interop.AllowWrite && property is JsString jsString)
+            {
+                _typeDescriptor.Remove(Target, jsString.ToString());
+            }
+        }
+
         public override JsValue Get(JsValue property, JsValue receiver)
         {
             if (property.IsInteger() && Target is IList list)
@@ -203,7 +211,12 @@ namespace Jint.Runtime.Interop
             {
                 if (_typeDescriptor.TryGetValue(Target, member, out var value))
                 {
-                    return new PropertyDescriptor(FromObject(_engine, value), PropertyFlag.OnlyEnumerable);
+                    var flags = PropertyFlag.Enumerable;
+                    if (_engine.Options.Interop.AllowWrite)
+                    {
+                        flags |= PropertyFlag.Configurable;
+                    }
+                    return new PropertyDescriptor(FromObject(_engine, value), flags);
                 }
             }
 

+ 12 - 0
Jint/Runtime/Interop/TypeDescriptor.cs

@@ -12,6 +12,7 @@ namespace Jint.Runtime.Interop
         private static readonly Type _stringType = typeof(string);
 
         private readonly MethodInfo? _tryGetValueMethod;
+        private readonly MethodInfo? _removeMethod;
         private readonly PropertyInfo? _keysAccessor;
         private readonly Type? _valueType;
 
@@ -25,6 +26,7 @@ namespace Jint.Runtime.Interop
                     && i.GenericTypeArguments[0] == _stringType)
                 {
                     _tryGetValueMethod = i.GetMethod("TryGetValue");
+                    _removeMethod = i.GetMethod("Remove");
                     _keysAccessor = i.GetProperty("Keys");
                     _valueType = i.GenericTypeArguments[1];
                     break;
@@ -105,6 +107,16 @@ namespace Jint.Runtime.Interop
             }
         }
 
+        public bool Remove(object target, string key)
+        {
+            if (_removeMethod is null)
+            {
+                return false;
+            }
+
+            return (bool) _removeMethod.Invoke(target , new object[] { key });
+        }
+
         public ICollection<string> GetKeys(object target)
         {
             if (!IsStringKeyedGenericDictionary)