Browse Source

Don't match explicit indexer properties when finding accessor (#886)

Marko Lahma 4 years ago
parent
commit
a8d89b2627
2 changed files with 54 additions and 33 deletions
  1. 41 26
      Jint.Tests/Runtime/InteropTests.cs
  2. 13 7
      Jint/Runtime/Interop/ObjectWrapper.cs

+ 41 - 26
Jint.Tests/Runtime/InteropTests.cs

@@ -43,7 +43,7 @@ namespace Jint.Tests.Runtime
         {
             _engine.Execute(source);
         }
-        
+
         public class Foo
         {
             public static Bar GetBar() => new Bar();
@@ -99,7 +99,7 @@ namespace Jint.Tests.Runtime
             const string json = "{\"foo\":5,\"bar\":\"A string\"}";
             var parsed = engine.Execute($"JSON.parse('{json}')").GetCompletionValue().ToObject();
             engine.SetValue(nameof(parsed), parsed);
-            
+
             var result = engine.Execute($"JSON.stringify({nameof(parsed)})").GetCompletionValue().AsString();
             Assert.Equal(json, result);
         }
@@ -117,7 +117,7 @@ namespace Jint.Tests.Runtime
                 assert(z === 'foo');
             ");
         }
-        
+
         [Fact]
         public void TypePropertyAccess()
         {
@@ -128,7 +128,7 @@ namespace Jint.Tests.Runtime
                 .Execute("userclass.TypeProperty.Name;")
                 .GetCompletionValue()
                 .AsString();
-            
+
             Assert.Equal("Person", result);
         }
 
@@ -807,7 +807,7 @@ namespace Jint.Tests.Runtime
 
             public Person this[int index] => _data[index];
         }
-        
+
         [Fact]
         public void CanAddArrayPrototypeForArrayLikeClrObjects()
         {
@@ -829,7 +829,7 @@ namespace Jint.Tests.Runtime
                 Age = 12,
                 Name = "John"
             };
-            
+
             dynamic obj = new
             {
                 values = new ReadOnlyList(person)
@@ -840,7 +840,7 @@ namespace Jint.Tests.Runtime
             var name = e.Execute("o.values.filter(x => x.age == 12)[0].name").GetCompletionValue().ToString();
             Assert.Equal("John", name);
         }
-        
+
         [Fact]
         public void CanAccessExpandoObject()
         {
@@ -2144,7 +2144,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldNotResolveToPrimitiveSymbol()
         {
-            var engine = new Engine(options => 
+            var engine = new Engine(options =>
                 options.AllowClr(typeof(FloatIndexer).GetTypeInfo().Assembly));
             var c = engine.Execute(@"
                 var domain = importNamespace('Jint.Tests.Runtime.Domain');
@@ -2205,9 +2205,9 @@ namespace Jint.Tests.Runtime
                 .ToObject();
 
             Assert.Equal("S1", result["supplier"]);
-            Assert.Equal("42", result["number"]);            
+            Assert.Equal("42", result["number"]);
         }
-        
+
         [Fact]
         public void ShouldSupportSpreadForDictionary2()
         {
@@ -2222,10 +2222,10 @@ namespace Jint.Tests.Runtime
                 .Execute("function getValue() { return {supplier: 'S1', ...state.invoice}; }")
                 .Invoke("getValue")
                 .ToObject();
-            
+
             Assert.Equal("S1", result["supplier"]);
-            Assert.Equal("42", result["number"]);    
-        }        
+            Assert.Equal("42", result["number"]);
+        }
 
         [Fact]
         public void ShouldSupportSpreadForObject()
@@ -2244,8 +2244,8 @@ namespace Jint.Tests.Runtime
                 .ToObject();
 
             Assert.Equal("S1", result["supplier"]);
-            Assert.Equal("Mike", result["Name"]);         
-            Assert.Equal(20d, result["Age"]);         
+            Assert.Equal("Mike", result["Name"]);
+            Assert.Equal(20d, result["Age"]);
         }
 
         [Fact]
@@ -2321,7 +2321,7 @@ namespace Jint.Tests.Runtime
 
                 return null;
             }));
-            
+
             engine.SetValue("m", new HiddenMembers());
 
             Assert.Equal("Orange", engine.Execute("m.Member1").GetCompletionValue().ToString());
@@ -2362,30 +2362,30 @@ namespace Jint.Tests.Runtime
             Assert.True(_engine.Execute("return o[0] == 'item1'").GetCompletionValue().AsBoolean());
             Assert.True(_engine.Execute("return o[1] == 'item2'").GetCompletionValue().AsBoolean());
         }
-        
+
         [Fact]
         public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
         {
             const string json = @"{ ""Type"": ""Cat"" }";
             var jObjectWithTypeProperty = JObject.Parse(json);
-        
+
             _engine.SetValue("o", jObjectWithTypeProperty);
-        
+
             var typeResult = _engine.Execute("o.Type").GetCompletionValue();
-            
+
             // JToken requires conversion
             Assert.Equal("Cat", TypeConverter.ToString(typeResult));
 
             // weak equality does conversions from native types
             Assert.True(_engine.Execute("o.Type == 'Cat'").GetCompletionValue().AsBoolean());
-        }        
+        }
 
         [Fact]
         public void IndexingBsonProperties()
         {
             const string jsonAnimals = @" { ""Animals"": [ { ""Id"": 1, ""Type"": ""Cat"" } ] }";
             var bsonAnimals = BsonDocument.Parse(jsonAnimals);
-            
+
             _engine.SetValue("animals", bsonAnimals["Animals"]);
 
             // weak equality does conversions from native types
@@ -2415,10 +2415,25 @@ namespace Jint.Tests.Runtime
             Assert.False(engine.Execute("test.ContainsKey('c')").GetCompletionValue().AsBoolean());
         }
 
+        [Fact]
+        public void CanAccessMemberNamedItemThroughExpando()
+        {
+            var parent = (IDictionary<string, object>) new ExpandoObject();
+            var child = (IDictionary<string, object>) new ExpandoObject();
+            var values = (IDictionary<string, object>) new ExpandoObject();
+
+            parent["child"] = child;
+            child["item"] = values;
+            values["title"] = "abc";
+
+            _engine.SetValue("parent", parent);
+            Assert.Equal("abc", _engine.Execute("parent.child.item.title").GetCompletionValue());
+        }
+
         private class DynamicClass : DynamicObject
         {
             private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
-            
+
             public override bool TryGetMember(GetMemberBinder binder, out object result)
             {
                 result = binder.Name;
@@ -2428,7 +2443,7 @@ namespace Jint.Tests.Runtime
                 }
                 return true;
             }
-        
+
             public override bool TrySetMember(SetMemberBinder binder, object value)
             {
                 _properties[binder.Name] = value;
@@ -2441,7 +2456,7 @@ namespace Jint.Tests.Runtime
                 return _properties.ContainsKey(key);
             }
         }
-            
+
         [Fact]
         public void IntegerEnumResolutionShouldWork()
         {
@@ -2534,7 +2549,7 @@ namespace Jint.Tests.Runtime
             _engine.SetValue("a", new Person { Name = "Name" });
             _engine.SetValue("b", new Person { Name = "Name" });
             _engine.Execute("const arr = [ null, a, undefined ];");
-            
+
             Assert.Equal(1, _engine.Execute("arr.filter(x => x == b).length").GetCompletionValue().AsNumber());
             Assert.Equal(1, _engine.Execute("arr.filter(x => x === b).length").GetCompletionValue().AsNumber());
 

+ 13 - 7
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -56,13 +56,13 @@ namespace Jint.Runtime.Interop
                 {
                     // can try utilize fast path
                     var accessor = GetAccessor(_engine, Target.GetType(), member);
-                    
+
                     // CanPut logic
                     if (!accessor.Writable || !_engine.Options._IsClrWriteAllowed)
                     {
                         return false;
                     }
-                    
+
                     accessor.SetValue(_engine, Target, value);
                     return true;
                 }
@@ -96,7 +96,7 @@ namespace Jint.Runtime.Interop
                 var index = (int) ((JsNumber) property)._value;
                 return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined;
             }
-            
+
             if (property.IsSymbol() && property != GlobalSymbolRegistry.Iterator)
             {
                 // wrapped objects cannot have symbol properties
@@ -111,7 +111,7 @@ namespace Jint.Runtime.Interop
                 {
                     return result;
                 }
-                
+
                 if (_properties is null || !_properties.ContainsKey(member))
                 {
                     // can try utilize fast path
@@ -158,7 +158,7 @@ namespace Jint.Runtime.Interop
             }
             else if (includeStrings && Target is IDictionary dictionary)
             {
-                // we take values exposed as dictionary keys only 
+                // we take values exposed as dictionary keys only
                 foreach (var key in dictionary.Keys)
                 {
                     if (_engine.ClrTypeConverter.TryConvert(key, typeof(string), CultureInfo.InvariantCulture, out var stringKey))
@@ -262,7 +262,7 @@ namespace Jint.Runtime.Interop
             }
 
             accessor = accessorFactory?.Invoke() ?? ResolvePropertyDescriptorFactory(engine, type, member);
-            
+
             // racy, we don't care, worst case we'll catch up later
             Interlocked.CompareExchange(ref Engine.ReflectionAccessors,
                 new Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor>(factories)
@@ -279,7 +279,7 @@ namespace Jint.Runtime.Interop
 
             // we can always check indexer if there's one, and then fall back to properties if indexer returns null
             IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer);
-            
+
             // properties and fields cannot be numbers
             if (!isNumber && TryFindStringPropertyAccessor(type, memberName, indexer, out var temp))
             {
@@ -303,6 +303,12 @@ namespace Jint.Runtime.Interop
             {
                 foreach (var iprop in iface.GetProperties())
                 {
+                    if (iprop.Name == "Item" && iprop.GetIndexParameters().Length == 1)
+                    {
+                        // never take indexers, should use the actual indexer
+                        continue;
+                    }
+
                     if (EqualsIgnoreCasing(iprop.Name, memberName))
                     {
                         list ??= new List<PropertyInfo>();