From 9984761e7dfb4e92057efa0aa45b31d4fd9512c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Fern=C3=A1ndez=20Corral?= Date: Thu, 30 Mar 2023 07:50:32 +0200 Subject: [PATCH] Support CLR object inheritance chain with prototypes (#1518) Co-authored-by: Marko Lahma --- Jint.Tests/Runtime/InstanceOfTests.cs | 32 +++++++++++-- Jint/Runtime/Interop/TypeReference.cs | 29 ++++++++--- .../Runtime/Interop/TypeReferencePrototype.cs | 48 +++++-------------- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/Jint.Tests/Runtime/InstanceOfTests.cs b/Jint.Tests/Runtime/InstanceOfTests.cs index d36be36e78..2d50840123 100644 --- a/Jint.Tests/Runtime/InstanceOfTests.cs +++ b/Jint.Tests/Runtime/InstanceOfTests.cs @@ -7,16 +7,28 @@ public class InstanceOfTests [Fact] public void ShouldSupportInheritanceChainUnderInterop() { - var a = new A(); - var b = new B(); - var c = new C(); - var engine = new Engine(); engine.SetValue("A", TypeReference.CreateTypeReference(engine, typeof(A))); + engine.SetValue("AToo", TypeReference.CreateTypeReference(engine, typeof(A))); engine.SetValue("B", TypeReference.CreateTypeReference(engine, typeof(B))); engine.SetValue("C", TypeReference.CreateTypeReference(engine, typeof(C))); + Assert.True(engine.Evaluate("A == A").AsBoolean()); + Assert.True(engine.Evaluate("A === A").AsBoolean()); + Assert.True(engine.Evaluate("A == AToo").AsBoolean()); + Assert.True(engine.Evaluate("A === AToo").AsBoolean()); + + Assert.True(engine.Evaluate("A.prototype instanceof A").AsBoolean()); + Assert.True(engine.Evaluate("B.prototype instanceof A").AsBoolean()); + Assert.False(engine.Evaluate("A.prototype instanceof B").AsBoolean()); + Assert.True(engine.Evaluate("C.prototype instanceof A").AsBoolean()); + Assert.True(engine.Evaluate("C.prototype instanceof B").AsBoolean()); + + var a = new A(); + var b = new B(); + var c = new C(); + engine.SetValue("a", a); engine.SetValue("b", b); engine.SetValue("c", c); @@ -32,6 +44,18 @@ public void ShouldSupportInheritanceChainUnderInterop() Assert.True(engine.Evaluate("c instanceof A").AsBoolean()); Assert.True(engine.Evaluate("c instanceof B").AsBoolean()); Assert.True(engine.Evaluate("c instanceof C").AsBoolean()); + + Assert.True(engine.Evaluate("new A() instanceof A").AsBoolean()); + Assert.False(engine.Evaluate("new A() instanceof B").AsBoolean()); + Assert.False(engine.Evaluate("new A() instanceof C").AsBoolean()); + + Assert.True(engine.Evaluate("new B() instanceof A").AsBoolean()); + Assert.True(engine.Evaluate("new B() instanceof B").AsBoolean()); + Assert.False(engine.Evaluate("new B() instanceof C").AsBoolean()); + + Assert.True(engine.Evaluate("new C() instanceof A").AsBoolean()); + Assert.True(engine.Evaluate("new C() instanceof B").AsBoolean()); + Assert.True(engine.Evaluate("new C() instanceof C").AsBoolean()); } public class A { } diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index d37d52a303..66e4fb97db 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -25,7 +25,7 @@ private TypeReference(Engine engine, Type type) _prototype = engine.Realm.Intrinsics.Function.PrototypeObject; _length = PropertyDescriptor.AllForbiddenDescriptor.NumberZero; - var proto = new JsObject(engine); + var proto = new TypeReferencePrototype(engine, this); _prototypeDescriptor = new PropertyDescriptor(proto, PropertyFlag.AllForbidden); PreventExtensions(); @@ -184,12 +184,21 @@ static ObjectInstance ObjectCreator(Engine engine, Realm realm, ObjectCreateStat ObjectCreator, new ObjectCreateState(this, arguments)); - return thisArgument; } private readonly record struct ObjectCreateState(TypeReference TypeReference, JsValue[] Arguments); + public override bool Equals(JsValue? obj) + { + if (obj is TypeReference typeReference) + { + return this.ReferenceType == typeReference.ReferenceType; + } + + return base.Equals(obj); + } + internal override bool OrdinaryHasInstance(JsValue v) { if (v is IObjectWrapper wrapper) @@ -295,8 +304,8 @@ private static ReflectionAccessor ResolveMemberAccessor(Engine engine, Type type return ConstantValueAccessor.NullAccessor; } - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; - return typeResolver.TryFindMemberAccessor(engine, type, name, bindingFlags, indexerToTry: null, out var accessor) + const BindingFlags BindingFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; + return typeResolver.TryFindMemberAccessor(engine, type, name, BindingFlags, indexerToTry: null, out var accessor) ? accessor : ConstantValueAccessor.NullAccessor; } @@ -306,16 +315,22 @@ private static ReflectionAccessor ResolveMemberAccessor(Engine engine, Type type private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) { var typeReference = thisObject as TypeReference; - var objectWrapper = arguments.At(0) as ObjectWrapper; + var other = arguments.At(0); - if (typeReference is null || objectWrapper is null) + if (typeReference is null) { return JsBoolean.False; } - var derivedType = objectWrapper.Target?.GetType(); var baseType = typeReference.ReferenceType; + var derivedType = other switch + { + ObjectWrapper wrapper => wrapper.Target.GetType(), + TypeReferencePrototype otherTypeReference => otherTypeReference.TypeReference.ReferenceType, + _ => null + }; + return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)); } } diff --git a/Jint/Runtime/Interop/TypeReferencePrototype.cs b/Jint/Runtime/Interop/TypeReferencePrototype.cs index 252687cbdd..562dc0fe52 100644 --- a/Jint/Runtime/Interop/TypeReferencePrototype.cs +++ b/Jint/Runtime/Interop/TypeReferencePrototype.cs @@ -1,40 +1,14 @@ -//using Jint.Native; -//using Jint.Native.Object; +using Jint.Native.Object; -//namespace Jint.Runtime.Interop -//{ -// public sealed class TypeReferencePrototype : ObjectInstance -// { -// private TypeReferencePrototype(Engine engine) -// : base(engine) -// { -// } +namespace Jint.Runtime.Interop; -// public static TypeReferencePrototype CreatePrototypeObject(Engine engine, TypeReference typeReferenceConstructor) -// { -// var obj = new TypeReferencePrototype(engine); -// obj.Prototype = engine.Object.PrototypeObject; -// obj.Extensible = false; +internal sealed class TypeReferencePrototype : ObjectInstance +{ + public TypeReferencePrototype(Engine engine, TypeReference typeReference) : base(engine) + { + TypeReference = typeReference; + _prototype = engine.Realm.Intrinsics.Object.PrototypeObject; + } -// obj.FastAddProperty("constructor", typeReferenceConstructor, true, false, true); - -// return obj; -// } - -// public void Configure() -// { -// FastAddProperty("toString", new ClrFunctionInstance(Engine, ToTypeReferenceString), true, false, true); -// } - -// private JsValue ToTypeReferenceString(JsValue thisObj, JsValue[] arguments) -// { -// var typeReference = thisObj.As(); -// if (typeReference == null) -// { -// ExceptionHelper.ThrowTypeError(Engine); -// } - -// return typeReference.Type.FullName; -// } -// } -//} + public TypeReference TypeReference { get; } +}