using System; using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using AspClassic.Scripting.Utils; namespace AspClassic.Scripting.Runtime; public sealed class DynamicOperations { private class SiteKey : IEquatable { internal readonly CallSiteBinder SiteBinder; private readonly Type _siteType; public int HitCount; public CallSite Site; public SiteKey(Type siteType, CallSiteBinder siteBinder) { SiteBinder = siteBinder; _siteType = siteType; } public override bool Equals(object obj) { return Equals(obj as SiteKey); } public override int GetHashCode() { return SiteBinder.GetHashCode() ^ _siteType.GetHashCode(); } public bool Equals(SiteKey other) { if (other == null) { return false; } if (other.SiteBinder.Equals(SiteBinder)) { return other._siteType == _siteType; } return false; } } private const int CleanupThreshold = 20; private const int RemoveThreshold = 2; private const int StopCleanupThreshold = 10; private const int ClearThreshold = 50; private readonly LanguageContext _lc; private Dictionary _sites = new Dictionary(); private int LastCleanup; private int SitesCreated; private Dictionary> _invokers = new Dictionary>(); public DynamicOperations(LanguageContext lc) { ContractUtils.RequiresNotNull(lc, "lc"); _lc = lc; } public object Invoke(object obj, params object[] parameters) { return GetInvoker(parameters.Length)(this, _lc.CreateInvokeBinder(new CallInfo(parameters.Length)), obj, parameters); } public object InvokeMember(object obj, string memberName, params object[] parameters) { return InvokeMember(obj, memberName, ignoreCase: false, parameters); } public object InvokeMember(object obj, string memberName, bool ignoreCase, params object[] parameters) { return GetInvoker(parameters.Length)(this, _lc.CreateCallBinder(memberName, ignoreCase, new CallInfo(parameters.Length)), obj, parameters); } public object CreateInstance(object obj, params object[] parameters) { return GetInvoker(parameters.Length)(this, _lc.CreateCreateBinder(new CallInfo(parameters.Length)), obj, parameters); } public object GetMember(object obj, string name) { return GetMember(obj, name, ignoreCase: false); } public T GetMember(object obj, string name) { return GetMember(obj, name, ignoreCase: false); } public bool TryGetMember(object obj, string name, out object value) { return TryGetMember(obj, name, ignoreCase: false, out value); } public bool ContainsMember(object obj, string name) { return ContainsMember(obj, name, ignoreCase: false); } public void RemoveMember(object obj, string name) { RemoveMember(obj, name, ignoreCase: false); } public void SetMember(object obj, string name, object value) { SetMember(obj, name, value, ignoreCase: false); } public void SetMember(object obj, string name, T value) { SetMember(obj, name, value, ignoreCase: false); } public object GetMember(object obj, string name, bool ignoreCase) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateGetMemberBinder(name, ignoreCase)); return orCreateSite.Target(orCreateSite, obj); } public T GetMember(object obj, string name, bool ignoreCase) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(typeof(T), null)); CallSite> orCreateSite2 = GetOrCreateSite(_lc.CreateGetMemberBinder(name, ignoreCase)); return orCreateSite.Target(orCreateSite, orCreateSite2.Target(orCreateSite2, obj)); } public bool TryGetMember(object obj, string name, bool ignoreCase, out object value) { try { value = GetMember(obj, name, ignoreCase); return true; } catch (MissingMemberException) { value = null; return false; } } public bool ContainsMember(object obj, string name, bool ignoreCase) { object value; return TryGetMember(obj, name, ignoreCase, out value); } public void RemoveMember(object obj, string name, bool ignoreCase) { CallSite> orCreateActionSite = GetOrCreateActionSite(_lc.CreateDeleteMemberBinder(name, ignoreCase)); orCreateActionSite.Target(orCreateActionSite, obj); } public void SetMember(object obj, string name, object value, bool ignoreCase) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateSetMemberBinder(name, ignoreCase)); orCreateSite.Target(orCreateSite, obj, value); } public void SetMember(object obj, string name, T value, bool ignoreCase) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateSetMemberBinder(name, ignoreCase)); orCreateSite.Target(orCreateSite, obj, value); } public T ConvertTo(object obj) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(typeof(T), null)); return orCreateSite.Target(orCreateSite, obj); } public object ConvertTo(object obj, Type type) { if (type.IsInterface || type.IsClass) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(type, null)); return orCreateSite.Target(orCreateSite, obj); } MemberInfo[] member = typeof(DynamicOperations).GetMember("ConvertTo"); for (int i = 0; i < member.Length; i++) { MethodInfo methodInfo = (MethodInfo)member[i]; if (methodInfo.IsGenericMethod) { try { return methodInfo.MakeGenericMethod(type).Invoke(this, new object[1] { obj }); } catch (TargetInvocationException ex) { throw ex.InnerException; } } } throw new InvalidOperationException(); } public bool TryConvertTo(object obj, out T result) { try { result = ConvertTo(obj); return true; } catch (ArgumentTypeException) { result = default(T); return false; } catch (InvalidCastException) { result = default(T); return false; } } public bool TryConvertTo(object obj, Type type, out object result) { try { result = ConvertTo(obj, type); return true; } catch (ArgumentTypeException) { result = null; return false; } catch (InvalidCastException) { result = null; return false; } } public T ExplicitConvertTo(object obj) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(typeof(T), true)); return orCreateSite.Target(orCreateSite, obj); } public object ExplicitConvertTo(object obj, Type type) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(type, true)); return orCreateSite.Target(orCreateSite, obj); } public bool TryExplicitConvertTo(object obj, Type type, out object result) { try { result = ExplicitConvertTo(obj, type); return true; } catch (ArgumentTypeException) { result = null; return false; } catch (InvalidCastException) { result = null; return false; } } public bool TryExplicitConvertTo(object obj, out T result) { try { result = ExplicitConvertTo(obj); return true; } catch (ArgumentTypeException) { result = default(T); return false; } catch (InvalidCastException) { result = default(T); return false; } } public T ImplicitConvertTo(object obj) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(typeof(T), false)); return orCreateSite.Target(orCreateSite, obj); } public object ImplicitConvertTo(object obj, Type type) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateConvertBinder(type, false)); return orCreateSite.Target(orCreateSite, obj); } public bool TryImplicitConvertTo(object obj, Type type, out object result) { try { result = ImplicitConvertTo(obj, type); return true; } catch (ArgumentTypeException) { result = null; return false; } catch (InvalidCastException) { result = null; return false; } } public bool TryImplicitConvertTo(object obj, out T result) { try { result = ImplicitConvertTo(obj); return true; } catch (ArgumentTypeException) { result = default(T); return false; } catch (InvalidCastException) { result = default(T); return false; } } public TResult DoOperation(ExpressionType operation, TTarget target) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateUnaryOperationBinder(operation)); return orCreateSite.Target(orCreateSite, target); } public TResult DoOperation(ExpressionType operation, TTarget target, TOther other) { CallSite> orCreateSite = GetOrCreateSite(_lc.CreateBinaryOperationBinder(operation)); return orCreateSite.Target(orCreateSite, target, other); } public string GetDocumentation(object o) { return _lc.GetDocumentation(o); } public IList GetCallSignatures(object o) { return _lc.GetCallSignatures(o); } public bool IsCallable(object o) { return _lc.IsCallable(o); } public IList GetMemberNames(object obj) { return _lc.GetMemberNames(obj); } public string Format(object obj) { return _lc.FormatObject(this, obj); } public CallSite> GetOrCreateSite(CallSiteBinder siteBinder) { return GetOrCreateSite(siteBinder, CallSite>.Create); } public CallSite> GetOrCreateActionSite(CallSiteBinder siteBinder) { return GetOrCreateSite(siteBinder, CallSite>.Create); } public CallSite> GetOrCreateSite(CallSiteBinder siteBinder) { return GetOrCreateSite(siteBinder, CallSite>.Create); } public CallSite> GetOrCreateSite(CallSiteBinder siteBinder) { return GetOrCreateSite(siteBinder, CallSite>.Create); } public CallSite GetOrCreateSite(CallSiteBinder siteBinder) where TSiteFunc : class { return GetOrCreateSite(siteBinder, CallSite.Create); } private T GetOrCreateSite(CallSiteBinder siteBinder, Func factory) where T : CallSite { SiteKey siteKey = new SiteKey(typeof(T), siteBinder); lock (_sites) { if (!_sites.TryGetValue(siteKey, out var value)) { SitesCreated++; if (SitesCreated < 0) { SitesCreated = 0; LastCleanup = 0; } siteKey.Site = factory(siteKey.SiteBinder); _sites[siteKey] = siteKey; } else { siteKey = value; } siteKey.HitCount++; CleanupNoLock(); } return (T)siteKey.Site; } private void CleanupNoLock() { if (_sites.Count <= 20 || LastCleanup >= SitesCreated - 20) { return; } LastCleanup = SitesCreated; int num = 0; foreach (SiteKey key in _sites.Keys) { num += key.HitCount; } int num2 = num / _sites.Count; if (num2 == 1 && _sites.Count > 50) { _sites.Clear(); return; } List list = null; foreach (SiteKey key2 in _sites.Keys) { if (key2.HitCount < num2 - 2) { if (list == null) { list = new List(); } list.Add(key2); if (list.Count > 10) { break; } } } if (list == null) { return; } foreach (SiteKey item in list) { _sites.Remove(item); } foreach (SiteKey key3 in _sites.Keys) { key3.HitCount = 0; } } private Func GetInvoker(int paramCount) { lock (_invokers) { if (!_invokers.TryGetValue(paramCount, out var value)) { ParameterExpression parameterExpression = Expression.Parameter(typeof(DynamicOperations)); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(CallSiteBinder)); ParameterExpression parameterExpression3 = Expression.Parameter(typeof(object)); ParameterExpression parameterExpression4 = Expression.Parameter(typeof(object[])); Type objectCallSiteDelegateType = ReflectionUtils.GetObjectCallSiteDelegateType(paramCount); ParameterExpression parameterExpression5 = Expression.Parameter(typeof(CallSite<>).MakeGenericType(objectCallSiteDelegateType)); Expression[] array = new Expression[paramCount + 2]; array[0] = parameterExpression5; array[1] = parameterExpression3; for (int i = 0; i < paramCount; i++) { array[i + 2] = Expression.ArrayIndex(parameterExpression4, Expression.Constant(i)); } MethodInfo genericMethodDefinition = new Func>>(GetOrCreateSite>).Method.GetGenericMethodDefinition(); return _invokers[paramCount] = Expression.Lambda>(Expression.Block(new ParameterExpression[1] { parameterExpression5 }, Expression.Assign(parameterExpression5, Expression.Call(parameterExpression, genericMethodDefinition.MakeGenericMethod(objectCallSiteDelegateType), parameterExpression2)), Expression.Invoke(Expression.Field(parameterExpression5, parameterExpression5.Type.GetField("Target")), array)), new ParameterExpression[4] { parameterExpression, parameterExpression2, parameterExpression3, parameterExpression4 }).Compile(); } return value; } } }