From 5ee35f1e52c4077e0c354b3e277ca052852723cf Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 25 Oct 2023 17:33:04 +0300 Subject: [PATCH] Keep track of MethodInfoFunctionInstance's target object (#1658) --- Jint.Tests/Runtime/InteropTests.cs | 9 +++++++++ Jint/Native/Error/ErrorInstance.cs | 2 +- Jint/Native/JsNull.cs | 2 +- Jint/Native/JsUndefined.cs | 2 +- Jint/Native/JsValue.cs | 2 +- Jint/Options.cs | 10 ++++++++-- Jint/Runtime/Interop/MethodInfoFunctionInstance.cs | 13 ++++++++----- Jint/Runtime/Interop/Reflection/MethodAccessor.cs | 2 +- 8 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index ac364f68b4..3882d75cac 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -3331,5 +3331,14 @@ public void AccessingInterfaceShouldContainExtendedInterfaces() var result = engine.Evaluate("const strings = Utils.GetStrings(); strings.Count;").AsNumber(); Assert.Equal(3, result); } + + [Fact] + public void CanDestructureInteropTargetMethod() + { + var engine = new Engine(); + engine.SetValue("test", new Utils()); + var result = engine.Evaluate("const { getStrings } = test; getStrings().Count;"); + Assert.Equal(3, result); + } } } diff --git a/Jint/Native/Error/ErrorInstance.cs b/Jint/Native/Error/ErrorInstance.cs index 508348193b..20fcdf4108 100644 --- a/Jint/Native/Error/ErrorInstance.cs +++ b/Jint/Native/Error/ErrorInstance.cs @@ -31,6 +31,6 @@ internal void InstallErrorCause(JsValue options) public override string ToString() { - return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject().ToString() ?? ""; + return Engine.Realm.Intrinsics.Error.PrototypeObject.ToString(this, Arguments.Empty).ToObject()?.ToString() ?? ""; } } diff --git a/Jint/Native/JsNull.cs b/Jint/Native/JsNull.cs index b08b12c09e..ee65963f12 100644 --- a/Jint/Native/JsNull.cs +++ b/Jint/Native/JsNull.cs @@ -8,7 +8,7 @@ internal JsNull() : base(Types.Null) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "null"; diff --git a/Jint/Native/JsUndefined.cs b/Jint/Native/JsUndefined.cs index 9772944b93..745f99dd45 100644 --- a/Jint/Native/JsUndefined.cs +++ b/Jint/Native/JsUndefined.cs @@ -8,7 +8,7 @@ internal JsUndefined() : base(Types.Undefined) { } - public override object ToObject() => null!; + public override object? ToObject() => null; public override string ToString() => "undefined"; diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 10c594f052..ae5e6b1ac4 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -157,7 +157,7 @@ public static JsValue FromObjectWithType(Engine engine, object? value, Type? typ /// Converts a to its underlying CLR value. /// /// The underlying CLR value of the instance. - public abstract object ToObject(); + public abstract object? ToObject(); /// /// Coerces boolean value from instance. diff --git a/Jint/Options.cs b/Jint/Options.cs index 74a477434e..980524e4ea 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -169,10 +169,16 @@ private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInsta foreach (var overloads in methods.GroupBy(x => x.Name)) { - string name = overloads.Key; PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function) { - var instance = new MethodInfoFunctionInstance(engine, objectType, name, MethodDescriptor.Build(overloads.ToList()), function); + var instance = new MethodInfoFunctionInstance( + engine, + objectType, + target: null, + overloads.Key, + methods: MethodDescriptor.Build(overloads.ToList()), + function); + return new PropertyDescriptor(instance, PropertyFlag.AllForbidden); } diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index 8d7385b2a0..264af018ea 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs @@ -10,6 +10,7 @@ namespace Jint.Runtime.Interop internal sealed class MethodInfoFunctionInstance : FunctionInstance { private readonly Type _targetType; + private readonly object? _target; private readonly string _name; private readonly MethodDescriptor[] _methods; private readonly ClrFunctionInstance? _fallbackClrFunctionInstance; @@ -17,19 +18,21 @@ internal sealed class MethodInfoFunctionInstance : FunctionInstance public MethodInfoFunctionInstance( Engine engine, Type targetType, + object? target, string name, MethodDescriptor[] methods, ClrFunctionInstance? fallbackClrFunctionInstance = null) : base(engine, engine.Realm, new JsString(name)) { _targetType = targetType; + _target = target; _name = name; _methods = methods; _fallbackClrFunctionInstance = fallbackClrFunctionInstance; _prototype = engine.Realm.Intrinsics.Function.PrototypeObject; } - private static bool IsGenericParameter(object argObj, Type parameterType) + private static bool IsGenericParameter(object? argObj, Type parameterType) { if (argObj is null) { @@ -49,7 +52,7 @@ private static bool IsGenericParameter(object argObj, Type parameterType) return false; } - private static void HandleGenericParameter(object argObj, Type parameterType, Type[] genericArgTypes) + private static void HandleGenericParameter(object? argObj, Type parameterType, Type[] genericArgTypes) { if (argObj is null) { @@ -94,7 +97,7 @@ private static void HandleGenericParameter(object argObj, Type parameterType, Ty } } - private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, object thisObj, JsValue[] arguments) + private static MethodBase ResolveMethod(MethodBase method, ParameterInfo[] methodParameters, JsValue[] arguments) { if (!method.IsGenericMethod) { @@ -156,7 +159,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var converter = Engine.ClrTypeConverter; - var thisObj = thisObject.ToObject(); + var thisObj = thisObject.ToObject() ?? _target; object?[]? parameters = null; foreach (var (method, arguments, _) in TypeConverter.FindBestMatch(_engine, _methods, ArgumentProvider)) { @@ -167,7 +170,7 @@ JsValue[] ArgumentProvider(MethodDescriptor method) } var argumentsMatch = true; - var resolvedMethod = ResolveMethod(method.Method, methodParameters, thisObj, arguments); + var resolvedMethod = ResolveMethod(method.Method, methodParameters, arguments); // TPC: if we're concerned about cost of MethodInfo.GetParameters() - we could only invoke it if this ends up being a generic method (i.e. they will be different in that scenario) methodParameters = resolvedMethod.GetParameters(); for (var i = 0; i < parameters.Length; i++) diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 18d400ca9c..e8823d3843 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs @@ -24,7 +24,7 @@ protected override void DoSetValue(object target, object? value) { } public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true) { - return new(new MethodInfoFunctionInstance(engine, _targetType, _name, _methods), PropertyFlag.AllForbidden); + return new(new MethodInfoFunctionInstance(engine, _targetType, target, _name, _methods), PropertyFlag.AllForbidden); } } }