瀏覽代碼

PropertyDescriptor improvements (#1648)

* PropertyDescriptor improvements
add PropertyFlag.NonData
now PropertyDescriptor's for Clr Field, Property and Indexer are accessor property descriptors

* add a test ClrPropertySideEffect to illustrate what the last commit for

* minor format changes
viruscamp 1 年之前
父節點
當前提交
89a1b615f6

+ 353 - 0
Jint.Tests/Runtime/PropertyDescriptorTests.cs

@@ -0,0 +1,353 @@
+using Jint.Native;
+using Jint.Native.Argument;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Descriptors.Specialized;
+using Jint.Runtime.Interop;
+using Jint.Tests.TestClasses;
+
+namespace Jint.Tests.Runtime;
+
+public class PropertyDescriptorTests
+{
+    public class TestClass
+    {
+        public static readonly TestClass Instance = new TestClass();
+        public string Method() => "Method";
+        public class NestedType { }
+
+        public readonly int fieldReadOnly = 8;
+        public int field = 42;
+
+        public string PropertyReadOnly => "PropertyReadOnly";
+        public string PropertyWriteOnly { set { } }
+        public string PropertyReadWrite { get; set; } = "PropertyReadWrite";
+
+        public IndexedPropertyReadOnly<int, int> IndexerReadOnly { get; }
+            = new((idx) => 42);
+        public IndexedPropertyWriteOnly<int, int> IndexerWriteOnly { get; }
+            = new((idx, v) => { });
+        public IndexedProperty<int, int> IndexerReadWrite { get; }
+            = new((idx) => 42, (idx, v) => { });
+    }
+
+    private readonly Engine _engine;
+
+    private readonly bool checkType = true;
+
+    public PropertyDescriptorTests()
+    {
+        _engine = new Engine(cfg => cfg.AllowClr(
+                    typeof(TestClass).Assembly,
+                    typeof(Console).Assembly,
+                    typeof(File).Assembly))
+                .SetValue("log", new Action<object>(Console.WriteLine))
+                .SetValue("assert", new Action<bool>(Assert.True))
+                .SetValue("equal", new Action<object, object>(Assert.Equal))
+                .SetValue("testClass", TestClass.Instance)
+        ;
+    }
+
+    [Fact]
+    public void PropertyDescriptorReadOnly()
+    {
+        var pd = _engine.Evaluate("""
+            Object.defineProperty({}, 'value', {
+              value: 42,
+              writable: false
+            })
+        """).AsObject().GetOwnProperty("value");
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+        Assert.Equal(false, pd.Writable);
+        Assert.Null(pd.Get);
+        Assert.Null(pd.Set);
+    }
+
+    [Fact]
+    public void PropertyDescriptorReadWrite()
+    {
+        var pd = _engine.Evaluate("""
+            Object.defineProperty({}, 'value', {
+              value: 42,
+              writable: true
+            })
+        """).AsObject().GetOwnProperty("value");
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+        Assert.Equal(true, pd.Writable);
+        Assert.Null(pd.Get);
+        Assert.Null(pd.Set);
+    }
+
+    [Fact]
+    public void UndefinedPropertyDescriptor()
+    {
+        var pd = PropertyDescriptor.Undefined;
+        // PropertyDescriptor.UndefinedPropertyDescriptor is private
+        //if (checkType) Assert.IsType<PropertyDescriptor.UndefinedPropertyDescriptor>(pd);
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void AllForbiddenDescriptor()
+    {
+        var pd = _engine.Evaluate("Object.getPrototypeOf('s')").AsObject().GetOwnProperty("length");
+        if (checkType) Assert.IsType<PropertyDescriptor.AllForbiddenDescriptor>(pd);
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void LazyPropertyDescriptor()
+    {
+        var pd = _engine.Evaluate("globalThis").AsObject().GetOwnProperty("decodeURI");
+        if (checkType) Assert.IsType<LazyPropertyDescriptor>(pd);
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ThrowerPropertyDescriptor()
+    {
+        var pd = _engine.Evaluate("Object.getPrototypeOf(function() {})").AsObject().GetOwnProperty("arguments");
+        if (checkType) Assert.IsType<GetSetPropertyDescriptor.ThrowerPropertyDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void GetSetPropertyDescriptorGetOnly()
+    {
+        var pd = _engine.Evaluate("""
+            Object.defineProperty({}, 'value', {
+              get() {}
+            })
+        """).AsObject().GetOwnProperty("value");
+        if (checkType) Assert.IsType<GetSetPropertyDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+        Assert.NotNull(pd.Get);
+        Assert.Null(pd.Set);
+    }
+
+    [Fact]
+    public void GetSetPropertyDescriptorSetOnly()
+    {
+        var pd = _engine.Evaluate("""
+            Object.defineProperty({}, 'value', {
+              set() {}
+            })
+        """).AsObject().GetOwnProperty("value");
+        if (checkType) Assert.IsType<GetSetPropertyDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+        Assert.Null(pd.Get);
+        Assert.NotNull(pd.Set);
+    }
+
+    [Fact]
+    public void GetSetPropertyDescriptorGetSet()
+    {
+        var pd = _engine.Evaluate("""
+            Object.defineProperty({}, 'value', {
+              get() {},
+              set() {}
+            })
+        """).AsObject().GetOwnProperty("value");
+        if (checkType) Assert.IsType<GetSetPropertyDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+        Assert.NotNull(pd.Get);
+        Assert.NotNull(pd.Set);
+    }
+
+    [Fact]
+    public void ClrAccessDescriptor()
+    {
+        JsValue ExtractClrAccessDescriptor(JsValue jsArugments)
+        {
+            var pd = ((ArgumentsInstance) jsArugments).ParameterMap.GetOwnProperty("0");
+            return new ObjectWrapper(_engine, pd);
+        }
+        _engine.SetValue("ExtractClrAccessDescriptor", ExtractClrAccessDescriptor);
+        var pdobj = _engine.Evaluate("""
+            (function(a) {
+              return ExtractClrAccessDescriptor(arguments);
+            })(42)
+        """);
+        var pd = (PropertyDescriptor) ((ObjectWrapper) pdobj).Target;
+        if (checkType) Assert.IsType<ClrAccessDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void PropertyDescriptorMethod()
+    {
+        var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'Method')");
+        CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("Method");
+        // use PropertyDescriptor to wrap method directly
+        //if (checkType) Assert.IsType<PropertyDescriptor>(pd);
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void PropertyDescriptorNestedType()
+    {
+        var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'NestedType')");
+        CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("NestedType");
+        // use PropertyDescriptor to wrap nested type directly
+        //if (checkType) Assert.IsType<PropertyDescriptor>(pd);
+        Assert.Equal(false, pd.IsAccessorDescriptor());
+        Assert.Equal(true, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorFieldReadOnly()
+    {
+        var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'fieldReadOnly')");
+        CheckPropertyDescriptor(pdField, false, true, false, false, true, false);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("fieldReadOnly");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorField()
+    {
+        var pdField = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'field')");
+        CheckPropertyDescriptor(pdField, false, true, true, false, true, true);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("field");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorPropertyReadOnly()
+    {
+        var pdPropertyReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadOnly')");
+        CheckPropertyDescriptor(pdPropertyReadOnly, false, true, false, false, true, false);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadOnly");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorPropertyWriteOnly()
+    {
+        var pdPropertyWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyWriteOnly')");
+        CheckPropertyDescriptor(pdPropertyWriteOnly, false, true, true, false, false, true);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyWriteOnly");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorPropertyReadWrite()
+    {
+        var pdPropertyReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'PropertyReadWrite')");
+        CheckPropertyDescriptor(pdPropertyReadWrite, false, true, true, false, true, true);
+
+        var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("PropertyReadWrite");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorIndexerReadOnly()
+    {
+        var pdIndexerReadOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadOnly, '1')");
+        CheckPropertyDescriptor(pdIndexerReadOnly, false, true, false, false, true, false);
+
+        var pd1 = _engine.Evaluate("testClass.IndexerReadOnly");
+        var pd = pd1.AsObject().GetOwnProperty("1");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorIndexerWriteOnly()
+    {
+        var pdIndexerWriteOnly = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerWriteOnly, '1')");
+        CheckPropertyDescriptor(pdIndexerWriteOnly, false, true, true, false, false, true);
+
+        var pd = _engine.Evaluate("testClass.IndexerWriteOnly").AsObject().GetOwnProperty("1");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    [Fact]
+    public void ReflectionDescriptorIndexerReadWrite()
+    {
+        var pdIndexerReadWrite = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass.IndexerReadWrite, 1)");
+        CheckPropertyDescriptor(pdIndexerReadWrite, false, true, true, false, true, true);
+
+        var pd = _engine.Evaluate("testClass.IndexerReadWrite").AsObject().GetOwnProperty("1");
+        if (checkType) Assert.IsType<ReflectionDescriptor>(pd);
+        Assert.Equal(true, pd.IsAccessorDescriptor());
+        Assert.Equal(false, pd.IsDataDescriptor());
+    }
+
+    private void CheckPropertyDescriptor(
+        JsValue jsPropertyDescriptor,
+        bool configurable,
+        bool enumerable,
+        bool writable,
+        bool hasValue,
+        bool hasGet,
+        bool hasSet
+    )
+    {
+        var pd = jsPropertyDescriptor.AsObject();
+
+        Assert.Equal(configurable, pd["configurable"].AsBoolean());
+        Assert.Equal(enumerable, pd["enumerable"].AsBoolean());
+        if (writable)
+        {
+            var writableActual = pd["writable"];
+            if (!writableActual.IsUndefined())
+            {
+                Assert.True(writableActual.AsBoolean());
+            }
+        }
+
+        Assert.Equal(hasValue, !pd["value"].IsUndefined());
+        Assert.Equal(hasGet, !pd["get"].IsUndefined());
+        Assert.Equal(hasSet, !pd["set"].IsUndefined());
+    }
+
+    [Fact]
+    public void DefinePropertyFromAccesorToData()
+    {
+        var pd = _engine.Evaluate("""
+            let o = {};
+            Object.defineProperty(o, 'foo', {
+              get() { return 1; },
+              configurable: true
+            });
+            Object.defineProperty(o, 'foo', {
+              value: 101
+            });
+            return Object.getOwnPropertyDescriptor(o, 'foo');
+        """);
+        Assert.Equal(101, pd.AsObject().Get("value").AsInteger());
+        CheckPropertyDescriptor(pd, true, false, false, true, false, false);
+    }
+}

+ 21 - 0
Jint.Tests/Runtime/ProxyTests.cs

@@ -233,6 +233,9 @@ public class ProxyTests
         public string StringValue => "StringValue";
         public int IntValue => 42424242; // avoid small numbers cache
         public TestClass ObjectWrapper => Instance;
+
+        private int x = 1;
+        public int PropertySideEffect => x++;
     }
 
     [Fact]
@@ -422,4 +425,22 @@ public class ProxyTests
             p.value = 42;
         """);
     }
+
+    [Fact]
+    public void ClrPropertySideEffect()
+    {
+        _engine.SetValue("testClass", TestClass.Instance);
+        _engine.Execute("""
+            const handler = {
+              get(target, property, receiver) {
+                return 2;
+              }
+            };
+            const p = new Proxy(testClass, handler);
+        """);
+
+        Assert.Equal(1, TestClass.Instance.PropertySideEffect); // first call to PropertySideEffect
+        Assert.Equal(2, _engine.Evaluate("p.PropertySideEffect").AsInteger()); // no call to PropertySideEffect
+        Assert.Equal(2, TestClass.Instance.PropertySideEffect); // second call to PropertySideEffect
+    }
 }

+ 49 - 0
Jint.Tests/Runtime/TestClasses/IndexerProperty.cs

@@ -0,0 +1,49 @@
+namespace Jint.Tests.TestClasses;
+
+public class IndexedProperty<TIndex, TValue>
+{
+    Action<TIndex, TValue> Setter { get; }
+    Func<TIndex, TValue> Getter { get; }
+
+    public IndexedProperty(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter)
+    {
+        Getter = getter;
+        Setter = setter;
+    }
+
+    public TValue this[TIndex i]
+    {
+        get => Getter(i);
+        set => Setter(i, value);
+    }
+}
+
+public class IndexedPropertyReadOnly<TIndex, TValue>
+{
+    Func<TIndex, TValue> Getter { get; }
+
+    public IndexedPropertyReadOnly(Func<TIndex, TValue> getter)
+    {
+        Getter = getter;
+    }
+
+    public TValue this[TIndex i]
+    {
+        get => Getter(i);
+    }
+}
+
+public class IndexedPropertyWriteOnly<TIndex, TValue>
+{
+    Action<TIndex, TValue> Setter { get; }
+
+    public IndexedPropertyWriteOnly(Action<TIndex, TValue> setter)
+    {
+        Setter = setter;
+    }
+
+    public TValue this[TIndex i]
+    {
+        set => Setter(i, value);
+    }
+}

+ 1 - 1
Jint/Native/Argument/ArgumentsInstance.cs

@@ -66,7 +66,7 @@ namespace Jint.Native.Argument
                     CreateDataProperty(JsString.Create(i), val);
                 }
 
-                DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.CustomJsValue));
+                DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None));
             }
             else
             {

+ 2 - 2
Jint/Native/Function/FunctionPrototype.cs

@@ -34,8 +34,8 @@ namespace Jint.Native.Function
                 ["apply"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "apply", Apply, 2, lengthFlags), propertyFlags),
                 ["call"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "call", CallImpl, 1, lengthFlags), propertyFlags),
                 ["bind"] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "bind", Bind, 1, lengthFlags), propertyFlags),
-                ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue),
-                ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue)
+                ["arguments"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable),
+                ["caller"] = new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.Configurable)
             };
             SetProperties(properties);
 

+ 1 - 1
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -49,7 +49,7 @@ namespace Jint.Native.Function
                 && function.Function is not ArrowFunctionExpression
                 && !function.Function.Generator)
             {
-                SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable | PropertyFlag.CustomJsValue));
+                SetProperty(KnownKeys.Arguments, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(engine, PropertyFlag.Configurable));
                 SetProperty(KnownKeys.Caller, new PropertyDescriptor(Undefined, PropertyFlag.Configurable));
             }
         }

+ 10 - 1
Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs

@@ -10,6 +10,7 @@ namespace Jint.Runtime.Descriptors
         public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = null, bool? configurable = null)
         : base(null, writable: null, enumerable: enumerable, configurable: configurable)
         {
+            _flags |= PropertyFlag.NonData;
             _get = get;
             _set = set;
         }
@@ -17,12 +18,18 @@ namespace Jint.Runtime.Descriptors
         internal GetSetPropertyDescriptor(JsValue? get, JsValue? set, PropertyFlag flags)
             : base(null, flags)
         {
+            _flags |= PropertyFlag.NonData;
+            _flags &= ~PropertyFlag.WritableSet;
+            _flags &= ~PropertyFlag.Writable;
             _get = get;
             _set = set;
         }
 
         public GetSetPropertyDescriptor(PropertyDescriptor descriptor) : base(descriptor)
         {
+            _flags |= PropertyFlag.NonData;
+            _flags &= ~PropertyFlag.WritableSet;
+            _flags &= ~PropertyFlag.Writable;
             _get = descriptor.Get;
             _set = descriptor.Set;
         }
@@ -45,8 +52,10 @@ namespace Jint.Runtime.Descriptors
             private readonly Engine _engine;
             private JsValue? _thrower;
 
-            public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags) : base(flags)
+            public ThrowerPropertyDescriptor(Engine engine, PropertyFlag flags)
+                : base(flags | PropertyFlag.CustomJsValue)
             {
+                _flags |= PropertyFlag.NonData;
                 _engine = engine;
             }
 

+ 6 - 2
Jint/Runtime/Descriptors/PropertyDescriptor.cs

@@ -21,7 +21,7 @@ namespace Jint.Runtime.Descriptors
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         protected PropertyDescriptor(PropertyFlag flags)
         {
-            _flags = flags;
+            _flags = flags & ~PropertyFlag.NonData;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -357,7 +357,7 @@ namespace Jint.Runtime.Descriptors
 
             if (desc.IsDataDescriptor())
             {
-                properties["value"] =  new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable);
+                properties["value"] = new PropertyDescriptor(desc.Value ?? JsValue.Undefined, PropertyFlag.ConfigurableEnumerableWritable);
                 if (desc._flags != PropertyFlag.None || desc.WritableSet)
                 {
                     properties["writable"] = new PropertyDescriptor(desc.Writable, PropertyFlag.ConfigurableEnumerableWritable);
@@ -392,6 +392,10 @@ namespace Jint.Runtime.Descriptors
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsDataDescriptor()
         {
+            if (_flags.HasFlag(PropertyFlag.NonData))
+            {
+                return false;
+            }
             return (_flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0
                    || (_flags & PropertyFlag.CustomJsValue) != 0 && !ReferenceEquals(CustomValue, null)
                    || !ReferenceEquals(_value, null);

+ 3 - 0
Jint/Runtime/Descriptors/PropertyFlag.cs

@@ -16,6 +16,9 @@ namespace Jint.Runtime.Descriptors
         // we can check for mutable binding and do some fast assignments
         MutableBinding = 512,
 
+        // mark PropertyDescriptor as non data to accelerate IsDataDescriptor and avoid the side effect of CustomValue
+        NonData = 1024,
+
         // common helpers
         AllForbidden = ConfigurableSet | EnumerableSet | WritableSet,
         ConfigurableEnumerableWritable = Configurable | Enumerable | Writable,

+ 1 - 0
Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs

@@ -19,6 +19,7 @@ namespace Jint.Runtime.Descriptors.Specialized
             string name)
             : base(value: null, PropertyFlag.Configurable)
         {
+            _flags |= PropertyFlag.NonData;
             _env = env;
             _engine = engine;
             _name = new EnvironmentRecord.BindingName(name);

+ 1 - 0
Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs

@@ -12,6 +12,7 @@ namespace Jint.Runtime.Descriptors.Specialized
         internal LazyPropertyDescriptor(object? state, Func<object?, JsValue> resolver, PropertyFlag flags)
             : base(null, flags | PropertyFlag.CustomJsValue)
         {
+            _flags &= ~PropertyFlag.NonData;
             _state = state;
             _resolver = resolver;
         }

+ 30 - 14
Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs

@@ -1,5 +1,6 @@
 using System.Reflection;
 using Jint.Native;
+using Jint.Runtime.Interop;
 using Jint.Runtime.Interop.Reflection;
 
 namespace Jint.Runtime.Descriptors.Specialized
@@ -17,31 +18,46 @@ namespace Jint.Runtime.Descriptors.Specialized
             bool enumerable)
             : base((enumerable ? PropertyFlag.Enumerable : PropertyFlag.None) | PropertyFlag.CustomJsValue)
         {
+            _flags |= PropertyFlag.NonData;
             _engine = engine;
             _reflectionAccessor = reflectionAccessor;
             _target = target;
-            Writable = reflectionAccessor.Writable && engine.Options.Interop.AllowWrite;
+
+            if (reflectionAccessor.Writable && engine.Options.Interop.AllowWrite)
+            {
+                Set = new SetterFunctionInstance(_engine, DoSet);
+            }
+            if (reflectionAccessor.Readable)
+            {
+                Get = new GetterFunctionInstance(_engine, DoGet);
+            }
         }
 
+        public override JsValue? Get { get; }
+        public override JsValue? Set { get; }
+
 
         protected internal override JsValue? CustomValue
         {
-            get
+            get => DoGet(null);
+            set => DoSet(null, value);
+        }
+
+        JsValue DoGet(JsValue? thisObj)
+        {
+            var value = _reflectionAccessor.GetValue(_engine, _target);
+            var type = _reflectionAccessor.MemberType;
+            return JsValue.FromObjectWithType(_engine, value, type);
+        }
+        void DoSet(JsValue? thisObj, JsValue? v)
+        {
+            try
             {
-                var value = _reflectionAccessor.GetValue(_engine, _target);
-                var type = _reflectionAccessor.MemberType;
-                return JsValue.FromObjectWithType(_engine, value, type);
+                _reflectionAccessor.SetValue(_engine, _target, v!);
             }
-            set
+            catch (TargetInvocationException exception)
             {
-                try
-                {
-                    _reflectionAccessor.SetValue(_engine, _target, value!);
-                }
-                catch (TargetInvocationException exception)
-                {
-                    ExceptionHelper.ThrowMeaningfulException(_engine, exception);
-                }
+                ExceptionHelper.ThrowMeaningfulException(_engine, exception);
             }
         }
     }

+ 2 - 0
Jint/Runtime/Interop/Reflection/IndexerAccessor.cs

@@ -119,6 +119,8 @@ namespace Jint.Runtime.Interop.Reflection
             return false;
         }
 
+        public override bool Readable => _indexer.CanRead;
+
         public override bool Writable => _indexer.CanWrite;
 
         protected override object? DoGetValue(object target)

+ 2 - 7
Jint/Runtime/Interop/Reflection/MethodAccessor.cs

@@ -18,14 +18,9 @@ namespace Jint.Runtime.Interop.Reflection
 
         public override bool Writable => false;
 
-        protected override object? DoGetValue(object target)
-        {
-            return null;
-        }
+        protected override object? DoGetValue(object target) => null;
 
-        protected override void DoSetValue(object target, object? value)
-        {
-        }
+        protected override void DoSetValue(object target, object? value) { }
 
         public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true)
         {

+ 7 - 5
Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs

@@ -1,3 +1,5 @@
+using Jint.Runtime.Descriptors;
+
 namespace Jint.Runtime.Interop.Reflection;
 
 internal sealed class NestedTypeAccessor : ReflectionAccessor
@@ -11,12 +13,12 @@ internal sealed class NestedTypeAccessor : ReflectionAccessor
 
     public override bool Writable => false;
 
-    protected override object? DoGetValue(object target)
-    {
-        return _typeReference;
-    }
+    protected override object? DoGetValue(object target) => null;
+
+    protected override void DoSetValue(object target, object? value) { }
 
-    protected override void DoSetValue(object target, object? value)
+    public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true)
     {
+        return new(_typeReference, PropertyFlag.AllForbidden);
     }
 }

+ 2 - 0
Jint/Runtime/Interop/Reflection/PropertyAccessor.cs

@@ -15,6 +15,8 @@ namespace Jint.Runtime.Interop.Reflection
             _propertyInfo = propertyInfo;
         }
 
+        public override bool Readable => _propertyInfo.CanRead;
+
         public override bool Writable => _propertyInfo.CanWrite;
 
         protected override object? DoGetValue(object target)

+ 2 - 0
Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs

@@ -28,6 +28,8 @@ namespace Jint.Runtime.Interop.Reflection
             _indexer = indexer;
         }
 
+        public virtual bool Readable => true;
+
         public abstract bool Writable { get; }
 
         protected abstract object? DoGetValue(object target);