InteropTests.MemberAccess.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using Jint.Native;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Interop;
  6. using Jint.Tests.Runtime.Domain;
  7. namespace Jint.Tests.Runtime;
  8. public partial class InteropTests
  9. {
  10. [Fact]
  11. public void ShouldHideSpecificMembers()
  12. {
  13. var engine = new Engine(options => options.SetMemberAccessor((e, target, member) =>
  14. {
  15. if (target is HiddenMembers)
  16. {
  17. if (member == nameof(HiddenMembers.Member2) || member == nameof(HiddenMembers.Method2))
  18. {
  19. return JsValue.Undefined;
  20. }
  21. }
  22. return null;
  23. }));
  24. engine.SetValue("m", new HiddenMembers());
  25. Assert.Equal("Member1", engine.Evaluate("m.Member1").ToString());
  26. Assert.Equal("undefined", engine.Evaluate("m.Member2").ToString());
  27. Assert.Equal("Method1", engine.Evaluate("m.Method1()").ToString());
  28. // check the method itself, not its invokation as it would mean invoking "undefined"
  29. Assert.Equal("undefined", engine.Evaluate("m.Method2").ToString());
  30. }
  31. [Fact]
  32. public void ShouldOverrideMembers()
  33. {
  34. var engine = new Engine(options => options.SetMemberAccessor((e, target, member) =>
  35. {
  36. if (target is HiddenMembers && member == nameof(HiddenMembers.Member1))
  37. {
  38. return "Orange";
  39. }
  40. return null;
  41. }));
  42. engine.SetValue("m", new HiddenMembers());
  43. Assert.Equal("Orange", engine.Evaluate("m.Member1").ToString());
  44. }
  45. [Fact]
  46. public void ShouldBeAbleToFilterMembers()
  47. {
  48. var engine = new Engine(options => options
  49. .SetTypeResolver(new TypeResolver
  50. {
  51. MemberFilter = member => !Attribute.IsDefined(member, typeof(ObsoleteAttribute))
  52. })
  53. );
  54. engine.SetValue("m", new HiddenMembers());
  55. Assert.True(engine.Evaluate("m.Field1").IsUndefined());
  56. Assert.True(engine.Evaluate("m.Member1").IsUndefined());
  57. Assert.True(engine.Evaluate("m.Method1").IsUndefined());
  58. Assert.True(engine.Evaluate("m.Field2").IsString());
  59. Assert.True(engine.Evaluate("m.Member2").IsString());
  60. Assert.True(engine.Evaluate("m.Method2()").IsString());
  61. // we forbid GetType by default
  62. Assert.True(engine.Evaluate("m.GetType").IsUndefined());
  63. }
  64. [Fact]
  65. public void ShouldBeAbleToExposeGetType()
  66. {
  67. var engine = new Engine(options =>
  68. {
  69. options.Interop.AllowGetType = true;
  70. options.Interop.AllowSystemReflection = true;
  71. });
  72. engine.SetValue("m", new HiddenMembers());
  73. Assert.True(engine.Evaluate("m.GetType").IsCallable);
  74. // reflection could bypass some safeguards
  75. Assert.Equal("Method1", engine.Evaluate("m.GetType().GetMethod('Method1').Invoke(m, [])").AsString());
  76. }
  77. [Fact]
  78. public void ShouldBeAbleToVaryGetTypeConfigurationBetweenEngines()
  79. {
  80. static string TestAllowGetTypeOption(bool allowGetType)
  81. {
  82. var uri = new Uri("https://github.com/sebastienros/jint");
  83. const string Input = nameof(uri) + ".GetType();";
  84. using var engine = new Engine(options => options.Interop.AllowGetType = allowGetType);
  85. engine.SetValue(nameof(uri), JsValue.FromObject(engine, uri));
  86. var result = engine.Evaluate(Input).ToString();
  87. return result;
  88. }
  89. Assert.Equal("System.Uri", TestAllowGetTypeOption(allowGetType: true));
  90. var ex = Assert.Throws<JavaScriptException>(() => TestAllowGetTypeOption(allowGetType: false));
  91. Assert.Equal("Property 'GetType' of object is not a function", ex.Message);
  92. }
  93. [Fact]
  94. public void ShouldProtectFromReflectionServiceUsage()
  95. {
  96. var engine = new Engine();
  97. engine.SetValue("m", new HiddenMembers());
  98. // we can get a type reference if it's exposed via property, bypassing GetType
  99. var type = engine.Evaluate("m.Type");
  100. Assert.IsType<ObjectWrapper>(type);
  101. var ex = Assert.Throws<InvalidOperationException>(() => engine.Evaluate("m.Type.Module.GetType().Module.GetType('System.DateTime')"));
  102. Assert.Equal("Cannot access System.Reflection namespace, check Engine's interop options", ex.Message);
  103. }
  104. [Fact]
  105. public void TypeReferenceShouldUseTypeResolverConfiguration()
  106. {
  107. var engine = new Engine(options =>
  108. {
  109. options.SetTypeResolver(new TypeResolver
  110. {
  111. MemberFilter = member => !Attribute.IsDefined(member, typeof(ObsoleteAttribute))
  112. });
  113. });
  114. engine.SetValue("EchoService", TypeReference.CreateTypeReference(engine, typeof(EchoService)));
  115. Assert.Equal("anyone there", engine.Evaluate("EchoService.Echo('anyone there')").AsString());
  116. Assert.Equal("anyone there", engine.Evaluate("EchoService.echo('anyone there')").AsString());
  117. Assert.True(engine.Evaluate("EchoService.ECHO").IsUndefined());
  118. Assert.True(engine.Evaluate("EchoService.Hidden").IsUndefined());
  119. }
  120. [Fact]
  121. public void CustomDictionaryPropertyAccessTests()
  122. {
  123. var engine = new Engine(options =>
  124. {
  125. options.AllowClr();
  126. });
  127. var dc = new CustomDictionary<float>();
  128. dc["a"] = 1;
  129. engine.SetValue("dc", dc);
  130. Assert.Equal(1, engine.Evaluate("dc.a"));
  131. Assert.Equal(1, engine.Evaluate("dc['a']"));
  132. Assert.NotEqual(JsValue.Undefined, engine.Evaluate("dc.Add"));
  133. Assert.NotEqual(0, engine.Evaluate("dc.Add"));
  134. Assert.Equal(JsValue.Undefined, engine.Evaluate("dc.b"));
  135. engine.Execute("dc.b = 5");
  136. engine.Execute("dc.Add('c', 10)");
  137. Assert.Equal(5, engine.Evaluate("dc.b"));
  138. Assert.Equal(10, engine.Evaluate("dc.c"));
  139. }
  140. [Fact]
  141. public void CanAccessBaseClassStaticFields()
  142. {
  143. var engine = new Engine(options =>
  144. {
  145. options.AllowClr();
  146. });
  147. engine.SetValue("B", TypeReference.CreateTypeReference(engine, typeof(InheritingFromClassWithStatics)));
  148. Assert.Equal(42, engine.Evaluate("B.a").AsNumber());
  149. Assert.Equal(24, engine.Evaluate("B.a = 24; B.a").AsNumber());
  150. }
  151. private class BaseClassWithStatics
  152. {
  153. #pragma warning disable CS0414 // Field is assigned but its value is never used
  154. public static int a = 42;
  155. #pragma warning restore CS0414 // Field is assigned but its value is never used
  156. }
  157. private class InheritingFromClassWithStatics : BaseClassWithStatics
  158. {
  159. }
  160. private static class EchoService
  161. {
  162. public static string Echo(string message) => message;
  163. [Obsolete]
  164. public static string Hidden(string message) => message;
  165. }
  166. private class CustomDictionary<TValue> : IDictionary<string, TValue>
  167. {
  168. readonly Dictionary<string, TValue> _dictionary = new Dictionary<string, TValue>();
  169. public TValue this[string key]
  170. {
  171. get
  172. {
  173. if (TryGetValue(key, out var val)) return val;
  174. // Normally, dictionary Item accessor throws an error when key does not exist
  175. // But this is a custom implementation
  176. return default;
  177. }
  178. set
  179. {
  180. _dictionary[key] = value;
  181. }
  182. }
  183. public ICollection<string> Keys => _dictionary.Keys;
  184. public ICollection<TValue> Values => _dictionary.Values;
  185. public int Count => _dictionary.Count;
  186. public bool IsReadOnly => false;
  187. public void Add(string key, TValue value) => _dictionary.Add(key, value);
  188. public void Add(KeyValuePair<string, TValue> item) => _dictionary.Add(item.Key, item.Value);
  189. public void Clear() => _dictionary.Clear();
  190. public bool Contains(KeyValuePair<string, TValue> item) => _dictionary.ContainsKey(item.Key);
  191. public bool ContainsKey(string key) => _dictionary.ContainsKey(key);
  192. public void CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex) => throw new NotImplementedException();
  193. public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
  194. public bool Remove(string key) => _dictionary.Remove(key);
  195. public bool Remove(KeyValuePair<string, TValue> item) => _dictionary.Remove(item.Key);
  196. public bool TryGetValue(string key, [MaybeNullWhen(false)] out TValue value) => _dictionary.TryGetValue(key, out value);
  197. IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();
  198. }
  199. public class ClassWithData
  200. {
  201. public int Age => 42;
  202. public DataType Data { get; set; }
  203. public class DataType
  204. {
  205. public string Value { get; set; }
  206. }
  207. }
  208. [Fact]
  209. public void NewTypedObjectFromUntypedInitializerShouldBeMapped()
  210. {
  211. var engine = new Engine();
  212. engine.SetValue("obj", new ClassWithData());
  213. engine.Execute("obj.Data = { Value: '123' };");
  214. var obj = engine.Evaluate("obj").ToObject() as ClassWithData;
  215. Assert.Equal("123", obj?.Data.Value);
  216. }
  217. [Fact]
  218. public void CanConfigureStrictAccess()
  219. {
  220. var engine = new Engine();
  221. engine.SetValue("obj", new ClassWithData());
  222. engine.Evaluate("obj.Age").AsNumber().Should().Be(42);
  223. engine.Evaluate("obj.AgeMissing").Should().Be(JsValue.Undefined);
  224. engine = new Engine(options =>
  225. {
  226. options.Interop.ThrowOnUnresolvedMember = true;
  227. });
  228. engine.SetValue("obj", new ClassWithData());
  229. engine.Evaluate("obj.Age").AsNumber().Should().Be(42);
  230. engine.Invoking(e => e.Evaluate("obj.AgeMissing")).Should().Throw<MissingMemberException>();
  231. }
  232. }