From 4eecccbdd4b9b3192de256e7e8894d42fa853294 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 27 Jun 2024 08:59:19 +0300 Subject: [PATCH] Add interop option ThrowOnUnresolvedMember (#1904) --- .../Runtime/InteropTests.MemberAccess.cs | 24 ++++++++++++++++++- Jint/Options.cs | 5 ++++ Jint/Runtime/Interop/TypeResolver.cs | 5 ++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Jint.Tests/Runtime/InteropTests.MemberAccess.cs b/Jint.Tests/Runtime/InteropTests.MemberAccess.cs index 88e7174ef8..f78d77d7f8 100644 --- a/Jint.Tests/Runtime/InteropTests.MemberAccess.cs +++ b/Jint.Tests/Runtime/InteropTests.MemberAccess.cs @@ -239,6 +239,8 @@ public TValue this[string key] public class ClassWithData { + public int Age => 42; + public DataType Data { get; set; } public class DataType @@ -253,10 +255,30 @@ public void NewTypedObjectFromUntypedInitializerShouldBeMapped() var engine = new Engine(); engine.SetValue("obj", new ClassWithData()); - engine.Execute(@"obj.Data = { Value: '123' };"); + engine.Execute("obj.Data = { Value: '123' };"); var obj = engine.Evaluate("obj").ToObject() as ClassWithData; Assert.Equal("123", obj?.Data.Value); } + + [Fact] + public void CanConfigureStrictAccess() + { + var engine = new Engine(); + + engine.SetValue("obj", new ClassWithData()); + engine.Evaluate("obj.Age").AsNumber().Should().Be(42); + engine.Evaluate("obj.AgeMissing").Should().Be(JsValue.Undefined); + + engine = new Engine(options => + { + options.Interop.ThrowOnUnresolvedMember = true; + }); + + engine.SetValue("obj", new ClassWithData()); + engine.Evaluate("obj.Age").AsNumber().Should().Be(42); + + engine.Invoking(e => e.Evaluate("obj.AgeMissing")).Should().Throw(); + } } } diff --git a/Jint/Options.cs b/Jint/Options.cs index b0775768e3..9b9bca7650 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -359,6 +359,11 @@ public class InteropOptions /// Should the Array prototype be attached instead of Object prototype to the wrapped interop objects when type looks suitable. Defaults to true. /// public bool AttachArrayPrototype { get; set; } = true; + + /// + /// Whether the engine should throw an error when a member is not found on a CLR object. Defaults to false. + /// + public bool ThrowOnUnresolvedMember { get; set; } } public class ConstraintOptions diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index b8f88ff9a7..2fed2aa082 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -262,6 +262,11 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( } } + if (engine.Options.Interop.ThrowOnUnresolvedMember) + { + throw new MissingMemberException($"Cannot access property '{memberName}' on type '{type.FullName}"); + } + return ConstantValueAccessor.NullAccessor; }