From d48ebd50ba5af240f484a3763227d2a53999a365 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Thu, 31 Aug 2023 16:36:24 +0800 Subject: [PATCH] Interface type wrapper can call object methods. (#1629) Fix #1626 --- .../Runtime/InteropExplicitTypeTests.cs | 60 ++++++++++++++++++- Jint/Runtime/Interop/TypeResolver.cs | 17 +++++- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs index 859294dec1..eef485018a 100644 --- a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs +++ b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs @@ -1,5 +1,6 @@ namespace Jint.Tests.Runtime; +using Jint.Native; using Jint.Runtime.Interop; public class InteropExplicitTypeTests @@ -154,9 +155,9 @@ public NullabeStruct() } public string name = "NullabeStruct"; - public string Name { get => name; } + public string Name => name; - string I1.Name { get => "NullabeStruct as I1"; } + string I1.Name => "NullabeStruct as I1"; } public class NullableHolder @@ -172,7 +173,7 @@ public void TypedObjectWrapperForNullableType() _engine.SetValue("nullableHolder", nullableHolder); _engine.SetValue("nullabeStruct", new NullabeStruct()); - Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null); + Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), JsValue.Null); _engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct"); Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name); } @@ -288,4 +289,57 @@ public void ClrHelperObjectToType() }); Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message); } + + public interface ICallObjectMethodFromInterface + { + ICallObjectMethodFromInterface Instance { get; } + // hide Object.GetHashCode + string GetHashCode(); + // overload Object.Equals + string Equals(); + } + public class CallObjectMethodFromInterface : ICallObjectMethodFromInterface + { + public ICallObjectMethodFromInterface Instance => this; + public override string ToString() => nameof(CallObjectMethodFromInterface); + public new string GetHashCode() => "new GetHashCode, hide Object.GetHashCode"; + public string Equals() => "overload Object.Equals"; + } + + // issue#1626 ToString method is now unavailable in some CLR Interop scenarios + [Fact] + public void CallObjectMethodFromInterfaceWrapper() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.ToString(), _engine.Evaluate("inst.Instance.ToString()")); + } + + [Fact] + public void CallInterfaceMethodWhichHideObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.GetHashCode(), _engine.Evaluate("inst.Instance.GetHashCode()")); + } + + [Fact(Skip = "TODO, no solution now.")] + public void CallObjectMethodHiddenByInterface() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal( + (inst.Instance as object).GetHashCode(), + _engine.Evaluate("clrHelper.unwrap(inst.Instance).GetHashCode()") + ); + } + + [Fact] + public void CallInterfaceMethodWhichOverloadObjectMethod() + { + var inst = new CallObjectMethodFromInterface(); + _engine.SetValue("inst", inst); + Assert.Equal(inst.Instance.Equals(), _engine.Evaluate("inst.Instance.Equals()")); + Assert.Equal(inst.Instance.Equals(inst), _engine.Evaluate("inst.Instance.Equals(inst)")); + } } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index af40e876ac..45c0e45d29 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -267,11 +267,11 @@ internal bool TryFindMemberAccessor( // if no properties were found then look for a method List? methods = null; - foreach (var m in type.GetMethods(bindingFlags)) + void AddMethod(MethodInfo m) { if (!Filter(engine, m)) { - continue; + return; } foreach (var name in typeResolverMemberNameCreator(m)) @@ -283,6 +283,10 @@ internal bool TryFindMemberAccessor( } } } + foreach (var m in type.GetMethods(bindingFlags)) + { + AddMethod(m); + } // TPC: need to grab the extension methods here - for overloads if (engine._extensionMethods.TryGetExtensionMethods(type, out var extensionMethods)) @@ -297,6 +301,15 @@ internal bool TryFindMemberAccessor( } } + // Add Object methods to interface + if (type.IsInterface) + { + foreach (var m in typeof(object).GetMethods(bindingFlags)) + { + AddMethod(m); + } + } + if (methods?.Count > 0) { accessor = new MethodAccessor(type, memberName, MethodDescriptor.Build(methods));