From 89a1b615f6b8f77367765df8099392ac52afeda2 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Tue, 17 Oct 2023 00:23:47 +0800 Subject: [PATCH] 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 --- Jint.Tests/Runtime/PropertyDescriptorTests.cs | 353 ++++++++++++++++++ Jint.Tests/Runtime/ProxyTests.cs | 21 ++ .../Runtime/TestClasses/IndexerProperty.cs | 49 +++ Jint/Native/Argument/ArgumentsInstance.cs | 2 +- Jint/Native/Function/FunctionPrototype.cs | 4 +- .../Native/Function/ScriptFunctionInstance.cs | 2 +- .../Descriptors/GetSetPropertyDescriptor.cs | 11 +- .../Runtime/Descriptors/PropertyDescriptor.cs | 8 +- Jint/Runtime/Descriptors/PropertyFlag.cs | 3 + .../Specialized/ClrAccessDescriptor.cs | 1 + .../Specialized/LazyPropertyDescriptor.cs | 1 + .../Specialized/ReflectionDescriptor.cs | 44 ++- .../Interop/Reflection/IndexerAccessor.cs | 2 + .../Interop/Reflection/MethodAccessor.cs | 9 +- .../Interop/Reflection/NestedTypeAccessor.cs | 12 +- .../Interop/Reflection/PropertyAccessor.cs | 2 + .../Interop/Reflection/ReflectionAccessor.cs | 2 + 17 files changed, 493 insertions(+), 33 deletions(-) create mode 100644 Jint.Tests/Runtime/PropertyDescriptorTests.cs create mode 100644 Jint.Tests/Runtime/TestClasses/IndexerProperty.cs diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs new file mode 100644 index 0000000000..1561a29869 --- /dev/null +++ b/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 IndexerReadOnly { get; } + = new((idx) => 42); + public IndexedPropertyWriteOnly IndexerWriteOnly { get; } + = new((idx, v) => { }); + public IndexedProperty 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(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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); + } +} diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index 3685956996..e569e8ae4f 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -233,6 +233,9 @@ class TestClass 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 void ProxyHandlerSetInvariantsReturnsFalseInNonStrictMode() 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 + } } diff --git a/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs new file mode 100644 index 0000000000..899e3cfa51 --- /dev/null +++ b/Jint.Tests/Runtime/TestClasses/IndexerProperty.cs @@ -0,0 +1,49 @@ +namespace Jint.Tests.TestClasses; + +public class IndexedProperty +{ + Action Setter { get; } + Func Getter { get; } + + public IndexedProperty(Func getter, Action setter) + { + Getter = getter; + Setter = setter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + set => Setter(i, value); + } +} + +public class IndexedPropertyReadOnly +{ + Func Getter { get; } + + public IndexedPropertyReadOnly(Func getter) + { + Getter = getter; + } + + public TValue this[TIndex i] + { + get => Getter(i); + } +} + +public class IndexedPropertyWriteOnly +{ + Action Setter { get; } + + public IndexedPropertyWriteOnly(Action setter) + { + Setter = setter; + } + + public TValue this[TIndex i] + { + set => Setter(i, value); + } +} diff --git a/Jint/Native/Argument/ArgumentsInstance.cs b/Jint/Native/Argument/ArgumentsInstance.cs index 41d83967a9..3bbe3b7032 100644 --- a/Jint/Native/Argument/ArgumentsInstance.cs +++ b/Jint/Native/Argument/ArgumentsInstance.cs @@ -66,7 +66,7 @@ protected override void Initialize() CreateDataProperty(JsString.Create(i), val); } - DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.CustomJsValue)); + DefinePropertyOrThrow(CommonProperties.Callee, new GetSetPropertyDescriptor.ThrowerPropertyDescriptor(_engine, PropertyFlag.None)); } else { diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 7ea20ac7e5..8bc50f9460 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -34,8 +34,8 @@ protected override void Initialize() ["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); diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index c284bdf750..d794635ada 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -49,7 +49,7 @@ internal ScriptFunctionInstance( && 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)); } } diff --git a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs index 8a3b51ea07..76a576e4e1 100644 --- a/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs @@ -10,6 +10,7 @@ public sealed class GetSetPropertyDescriptor : PropertyDescriptor 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 @@ public GetSetPropertyDescriptor(JsValue? get, JsValue? set, bool? enumerable = n 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 @@ internal sealed class ThrowerPropertyDescriptor : PropertyDescriptor 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; } diff --git a/Jint/Runtime/Descriptors/PropertyDescriptor.cs b/Jint/Runtime/Descriptors/PropertyDescriptor.cs index f9dcf6fe71..57543f2fca 100644 --- a/Jint/Runtime/Descriptors/PropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/PropertyDescriptor.cs @@ -21,7 +21,7 @@ public PropertyDescriptor() : this(PropertyFlag.None) [MethodImpl(MethodImplOptions.AggressiveInlining)] protected PropertyDescriptor(PropertyFlag flags) { - _flags = flags; + _flags = flags & ~PropertyFlag.NonData; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -357,7 +357,7 @@ public static JsValue FromPropertyDescriptor(Engine engine, PropertyDescriptor d 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 @@ public bool IsAccessorDescriptor() [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); diff --git a/Jint/Runtime/Descriptors/PropertyFlag.cs b/Jint/Runtime/Descriptors/PropertyFlag.cs index ac1a6231e3..926d787ec0 100644 --- a/Jint/Runtime/Descriptors/PropertyFlag.cs +++ b/Jint/Runtime/Descriptors/PropertyFlag.cs @@ -16,6 +16,9 @@ public enum PropertyFlag // 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, diff --git a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs index 3dee3f8192..85f41a0340 100644 --- a/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/ClrAccessDescriptor.cs @@ -19,6 +19,7 @@ public ClrAccessDescriptor( string name) : base(value: null, PropertyFlag.Configurable) { + _flags |= PropertyFlag.NonData; _env = env; _engine = engine; _name = new EnvironmentRecord.BindingName(name); diff --git a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs index 2d13df852f..e427546bad 100644 --- a/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs +++ b/Jint/Runtime/Descriptors/Specialized/LazyPropertyDescriptor.cs @@ -12,6 +12,7 @@ internal sealed class LazyPropertyDescriptor : PropertyDescriptor internal LazyPropertyDescriptor(object? state, Func resolver, PropertyFlag flags) : base(null, flags | PropertyFlag.CustomJsValue) { + _flags &= ~PropertyFlag.NonData; _state = state; _resolver = resolver; } diff --git a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs b/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs index 4885b4f52f..abfe7b4bb5 100644 --- a/Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs +++ b/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 @@ public ReflectionDescriptor( 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); } } } diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs index 162f496117..2ecb0892d0 100644 --- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs @@ -119,6 +119,8 @@ internal static bool TryFindIndexer( return false; } + public override bool Readable => _indexer.CanRead; + public override bool Writable => _indexer.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 3a7a8b80df..18d400ca9c 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs @@ -18,14 +18,9 @@ public MethodAccessor(Type targetType, string name, MethodDescriptor[] methods) 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) { diff --git a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs index c4d7a66e65..6d68e40083 100644 --- a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs +++ b/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 @@ public NestedTypeAccessor(TypeReference typeReference, string name) : base(typeo 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); } } diff --git a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs index 0c220ea2be..9f33768947 100644 --- a/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/PropertyAccessor.cs @@ -15,6 +15,8 @@ public PropertyAccessor( _propertyInfo = propertyInfo; } + public override bool Readable => _propertyInfo.CanRead; + public override bool Writable => _propertyInfo.CanWrite; protected override object? DoGetValue(object target) diff --git a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs index 233f6f05ed..b768f55b3e 100644 --- a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs @@ -28,6 +28,8 @@ protected ReflectionAccessor( _indexer = indexer; } + public virtual bool Readable => true; + public abstract bool Writable { get; } protected abstract object? DoGetValue(object target);