// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.ClearScript.Util; namespace Microsoft.ClearScript { /// /// Provides the base implementation for all script engines. /// public abstract class ScriptEngine : IScriptEngine, IHostContext { #region data private ScriptObject engineInternal; private Type accessContext; private ScriptAccess defaultAccess; private bool enforceAnonymousTypeAccess; private bool exposeHostObjectStaticMembers; private bool marshalEnumAsUnderlyingType; private bool acceptEnumAsUnderlyingType; private CustomAttributeLoader customAttributeLoader; private DocumentSettings documentSettings; private readonly DocumentSettings defaultDocumentSettings = new(); private static readonly IUniqueNameManager nameManager = new UniqueNameManager(); private static readonly object nullHostObjectProxy = new(); [ThreadStatic] private static ScriptEngine currentEngine; #endregion #region constructors /// /// Initializes a new script engine instance. /// /// A name to associate with the instance. Currently, this name is used only as a label in presentation contexts such as debugger user interfaces. [Obsolete("Use ScriptEngine(string name, string fileNameExtensions) instead.")] protected ScriptEngine(string name) : this(name, null) { } /// /// Initializes a new script engine instance with the specified list of supported file name extensions. /// /// A name to associate with the instance. Currently, this name is used only as a label in presentation contexts such as debugger user interfaces. /// A semicolon-delimited list of supported file name extensions. protected ScriptEngine(string name, string fileNameExtensions) { Name = nameManager.GetUniqueName(name, GetType().GetRootName()); defaultDocumentSettings.FileNameExtensions = fileNameExtensions; extensionMethodTable = realExtensionMethodTable = new ExtensionMethodTable(); } #endregion #region public members /// /// Gets the script engine that is invoking a host member on the current thread. /// /// /// If multiple script engines are invoking host members on the current thread, this /// property gets the one responsible for the most deeply nested invocation. If no script /// engines are invoking host members on the current thread, this property returns /// null. /// public static ScriptEngine Current => currentEngine; #endregion #region IScriptEngine implementation /// public string Name { get; } /// public abstract string FileNameExtension { get; } /// public Type AccessContext { get => accessContext; set { accessContext = value; OnAccessSettingsChanged(); } } /// public ScriptAccess DefaultAccess { get => defaultAccess; set { defaultAccess = value; OnAccessSettingsChanged(); } } /// public bool EnforceAnonymousTypeAccess { get => enforceAnonymousTypeAccess; set { enforceAnonymousTypeAccess = value; OnAccessSettingsChanged(); } } /// public bool ExposeHostObjectStaticMembers { get => exposeHostObjectStaticMembers; set { exposeHostObjectStaticMembers = value; OnAccessSettingsChanged(); } } /// public bool DisableExtensionMethods { get => extensionMethodTable == emptyExtensionMethodTable; set { var newExtensionMethodTable = value ? emptyExtensionMethodTable : realExtensionMethodTable; if (newExtensionMethodTable != extensionMethodTable) { ScriptInvoke( static ctx => { if (ctx.newExtensionMethodTable != ctx.self.extensionMethodTable) { ctx.self.extensionMethodTable = ctx.newExtensionMethodTable; ctx.self.ClearMethodBindCache(); ctx.self.OnAccessSettingsChanged(); } }, (self: this, newExtensionMethodTable) ); } } } /// public bool FormatCode { get; set; } /// public bool AllowReflection { get; set; } /// public bool DisableTypeRestriction { get; set; } /// public bool DisableListIndexTypeRestriction { get; set; } /// public bool EnableNullResultWrapping { get; set; } /// public bool DisableFloatNarrowing { get; set; } /// public bool DisableDynamicBinding { get; set; } /// public bool UseReflectionBindFallback { get; set; } /// public bool MarshalEnumAsUnderlyingType { get => marshalEnumAsUnderlyingType; set { marshalEnumAsUnderlyingType = value; OnAccessSettingsChanged(); } } /// public bool AcceptEnumAsUnderlyingType { get => acceptEnumAsUnderlyingType; set { if (value != acceptEnumAsUnderlyingType) { ScriptInvoke( static ctx => { if (ctx.value != ctx.self.acceptEnumAsUnderlyingType) { ctx.self.acceptEnumAsUnderlyingType = ctx.value; ctx.self.ClearMethodBindCache(); ctx.self.OnAccessSettingsChanged(); } }, (self: this, value) ); } } } /// public bool EnableAutoHostVariables { get; set; } /// public object UndefinedImportValue { get; set; } = Undefined.Value; /// public object NullImportValue { get; set; } /// public object NullExportValue { get; set; } /// public object VoidResultValue { get; set; } = VoidResult.Value; /// public ContinuationCallback ContinuationCallback { get; set; } /// public abstract dynamic Script { get; } /// public abstract ScriptObject Global { get; } /// public DocumentSettings DocumentSettings { get => documentSettings ?? defaultDocumentSettings; set => documentSettings = value; } /// public CustomAttributeLoader CustomAttributeLoader { get => customAttributeLoader; set { customAttributeLoader = value; OnAccessSettingsChanged(); } } /// public object HostData { get; set; } /// public void AddHostObject(string itemName, object target) { AddHostObject(itemName, HostItemFlags.None, target); } /// public void AddHostObject(string itemName, HostItemFlags flags, object target) { MiscHelpers.VerifyNonNullArgument(target, nameof(target)); AddHostItem(itemName, flags, target); } /// public void AddRestrictedHostObject(string itemName, T target) { AddRestrictedHostObject(itemName, HostItemFlags.None, target); } /// public void AddRestrictedHostObject(string itemName, HostItemFlags flags, T target) { AddHostItem(itemName, flags, HostItem.Wrap(this, target, typeof(T))); } /// public void AddCOMObject(string itemName, string progID) { AddCOMObject(itemName, HostItemFlags.None, progID); } /// public void AddCOMObject(string itemName, string progID, string serverName) { AddCOMObject(itemName, HostItemFlags.None, progID, serverName); } /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID) { AddCOMObject(itemName, flags, progID, null); } /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(progID, serverName)); } /// public void AddCOMObject(string itemName, Guid clsid) { AddCOMObject(itemName, HostItemFlags.None, clsid); } /// public void AddCOMObject(string itemName, Guid clsid, string serverName) { AddCOMObject(itemName, HostItemFlags.None, clsid, serverName); } /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid) { AddCOMObject(itemName, flags, clsid, null); } /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(clsid, serverName)); } /// public void AddHostType(Type type) { AddHostType(HostItemFlags.None, type); } /// public void AddHostType(HostItemFlags flags, Type type) { AddHostType(type.GetRootName(), flags, type); } /// public void AddHostType(string itemName, Type type) { AddHostType(itemName, HostItemFlags.None, type); } /// public void AddHostType(string itemName, HostItemFlags flags, Type type) { MiscHelpers.VerifyNonNullArgument(type, nameof(type)); AddHostItem(itemName, flags, HostType.Wrap(type)); } /// public void AddHostType(string itemName, string typeName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, typeArgs); } /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, null, false, typeArgs)); } /// public void AddHostType(string itemName, string typeName, string assemblyName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, assemblyName, typeArgs); } /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, string assemblyName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs)); } /// public void AddHostTypes(params Type[] types) { if (types is not null) { foreach (var type in types) { if (type is not null) { AddHostType(type); } } } } /// public void AddCOMType(string itemName, string progID) { AddCOMType(itemName, HostItemFlags.None, progID); } /// public void AddCOMType(string itemName, string progID, string serverName) { AddCOMType(itemName, HostItemFlags.None, progID, serverName); } /// public void AddCOMType(string itemName, HostItemFlags flags, string progID) { AddCOMType(itemName, flags, progID, null); } /// public void AddCOMType(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(progID, serverName))); } /// public void AddCOMType(string itemName, Guid clsid) { AddCOMType(itemName, HostItemFlags.None, clsid); } /// public void AddCOMType(string itemName, Guid clsid, string serverName) { AddCOMType(itemName, HostItemFlags.None, clsid, serverName); } /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid) { AddCOMType(itemName, flags, clsid, null); } /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(clsid, serverName))); } /// public void Execute(string code) { Execute(null, code); } /// public void Execute(string documentName, string code) { Execute(documentName, false, code); } /// public void Execute(string documentName, bool discard, string code) { Execute(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// public void Execute(DocumentInfo documentInfo, string code) { Execute(documentInfo.MakeUnique(this), code, false); } /// public void ExecuteDocument(string specifier) { ExecuteDocument(specifier, null); } /// public void ExecuteDocument(string specifier, DocumentCategory category) { ExecuteDocument(specifier, category, null); } /// public void ExecuteDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier"); var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback); Execute(document.Info, document.GetTextContents()); } /// public virtual string ExecuteCommand(string command) { var documentInfo = new DocumentInfo("Command") { Flags = DocumentFlags.IsTransient }; return GetCommandResultString(Evaluate(documentInfo.MakeUnique(this), command, false)); } /// public object Evaluate(string code) { return Evaluate(null, code); } /// public object Evaluate(string documentName, string code) { return Evaluate(documentName, true, code); } /// public object Evaluate(string documentName, bool discard, string code) { return Evaluate(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// public object Evaluate(DocumentInfo documentInfo, string code) { return Evaluate(documentInfo.MakeUnique(this, DocumentFlags.IsTransient), code, true); } /// public object EvaluateDocument(string specifier) { return EvaluateDocument(specifier, null); } /// public object EvaluateDocument(string specifier, DocumentCategory category) { return EvaluateDocument(specifier, category, null); } /// public object EvaluateDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier"); var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback); return Evaluate(document.Info, document.GetTextContents()); } /// public object Invoke(string funcName, params object[] args) { MiscHelpers.VerifyNonBlankArgument(funcName, nameof(funcName), "Invalid function name"); return Global.InvokeMethod(funcName, args ?? ArrayHelpers.GetEmptyArray()); } /// public abstract string GetStackTrace(); /// public abstract void Interrupt(); /// public abstract void CollectGarbage(bool exhaustive); #endregion #region internal members internal ScriptObject EngineInternal => engineInternal ?? (engineInternal = (ScriptObject)Global["EngineInternal"]); internal abstract IUniqueNameManager DocumentNameManager { get; } internal virtual bool EnumerateInstanceMethods => true; internal virtual bool EnumerateExtensionMethods => EnumerateInstanceMethods; internal virtual bool UseCaseInsensitiveMemberBinding => false; internal abstract void AddHostItem(string itemName, HostItemFlags flags, object item); internal object PrepareResult(T result, ScriptMemberFlags flags, bool isListIndexResult) { return PrepareResult(result, typeof(T), flags, isListIndexResult); } internal virtual object PrepareResult(object result, Type type, ScriptMemberFlags flags, bool isListIndexResult) { var wrapNull = flags.HasAllFlags(ScriptMemberFlags.WrapNullResult) || EnableNullResultWrapping; if (wrapNull && (result is null)) { return HostObject.WrapResult(null, type, true); } if (!flags.HasAllFlags(ScriptMemberFlags.ExposeRuntimeType) && !DisableTypeRestriction && (!isListIndexResult || !DisableListIndexTypeRestriction)) { return HostObject.WrapResult(result, type, wrapNull); } return result; } internal abstract object MarshalToScript(object obj, HostItemFlags flags); internal object MarshalToScript(object obj) { var hostItem = obj as HostItem; return MarshalToScript(obj, hostItem?.Flags ?? HostItemFlags.None); } internal object[] MarshalToScript(object[] args) { return args.Select(MarshalToScript).ToArray(); } internal abstract object MarshalToHost(object obj, bool preserveHostTarget); internal object[] MarshalToHost(object[] args, bool preserveHostTargets) { return args.Select(arg => MarshalToHost(arg, preserveHostTargets)).ToArray(); } internal abstract object Execute(UniqueDocumentInfo documentInfo, string code, bool evaluate); internal abstract object ExecuteRaw(UniqueDocumentInfo documentInfo, string code, bool evaluate); internal object Evaluate(UniqueDocumentInfo documentInfo, string code, bool marshalResult) { var result = Execute(documentInfo, code, true); if (marshalResult) { result = MarshalToHost(result, false); } return result; } internal string GetCommandResultString(object result) { if (result is HostItem hostItem) { if (hostItem.Target is IHostVariable) { return result.ToString(); } } var marshaledResult = MarshalToHost(result, false); if (marshaledResult is VoidResult) { return null; } if (marshaledResult is null) { return "[null]"; } if (marshaledResult is Undefined) { return marshaledResult.ToString(); } if (marshaledResult is ScriptItem) { return "[ScriptObject]"; } return result.ToString(); } internal void RequestInterrupt() { // Some script engines don't support IActiveScript::InterruptScriptThread(). This // method provides an alternate mechanism based on IActiveScriptSiteInterruptPoll. var tempScriptFrame = CurrentScriptFrame; if (tempScriptFrame is not null) { tempScriptFrame.InterruptRequested = true; } } internal void CheckReflection() { if (!AllowReflection) { throw new UnauthorizedAccessException("Use of reflection is prohibited in this script engine"); } } internal virtual void OnAccessSettingsChanged() { ClearConstructorBindCache(); ClearPropertyBindCache(); } #endregion #region host-side invocation internal virtual void HostInvoke(Action action) { action(); } internal virtual void HostInvoke(Action action, in TArg arg) { action(arg); } internal virtual TResult HostInvoke(Func func) { return func(); } internal virtual TResult HostInvoke(Func func, in TArg arg) { return func(arg); } #endregion #region script-side invocation internal ScriptFrame CurrentScriptFrame { get; private set; } internal ValueScope CreateEngineScope() { return ScopeFactory.Create(static engine => MiscHelpers.Exchange(ref currentEngine, engine), static previousEngine => currentEngine = previousEngine, this); } internal virtual void ScriptInvoke(Action action) { using (CreateEngineScope()) { ScriptInvokeInternal(action); } } internal virtual void ScriptInvoke(Action action, in TArg arg) { using (CreateEngineScope()) { ScriptInvokeInternal(action, arg); } } internal virtual TResult ScriptInvoke(Func func) { using (CreateEngineScope()) { return ScriptInvokeInternal(func); } } internal virtual TResult ScriptInvoke(Func func, in TArg arg) { using (CreateEngineScope()) { return ScriptInvokeInternal(func, arg); } } internal void ScriptInvokeInternal(Action action) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { action(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal void ScriptInvokeInternal(Action action, in TArg arg) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { action(arg); } finally { CurrentScriptFrame = previousScriptFrame; } } internal TResult ScriptInvokeInternal(Func func) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { return func(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal TResult ScriptInvokeInternal(Func func, in TArg arg) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { return func(arg); } finally { CurrentScriptFrame = previousScriptFrame; } } internal void ThrowScriptError() { if (CurrentScriptFrame is not null) { ThrowScriptError(CurrentScriptFrame.ScriptError); } } internal static void ThrowScriptError(IScriptEngineException scriptError) { if (scriptError is not null) { if (scriptError is ScriptInterruptedException) { throw new ScriptInterruptedException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptExceptionAsObject, scriptError.InnerException); } throw new ScriptEngineException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptExceptionAsObject, scriptError.InnerException); } } #endregion #region synchronized invocation internal virtual void SyncInvoke(Action action) { action(); } internal virtual T SyncInvoke(Func func) { return func(); } #endregion #region enumeration settings internal object EnumerationSettingsToken { get; private set; } = new(); internal void OnEnumerationSettingsChanged() { EnumerationSettingsToken = new object(); } #endregion #region extension method table private static readonly ExtensionMethodTable emptyExtensionMethodTable = new(); private readonly ExtensionMethodTable realExtensionMethodTable; private ExtensionMethodTable extensionMethodTable; internal void ProcessExtensionMethodType(Type type) { if (extensionMethodTable != emptyExtensionMethodTable) { if (extensionMethodTable.ProcessType(this, type)) { ClearMethodBindCache(); } } } internal ExtensionMethodSummary ExtensionMethodSummary => extensionMethodTable.Summary; internal void RebuildExtensionMethodSummary() { if (extensionMethodTable != emptyExtensionMethodTable) { extensionMethodTable.RebuildSummary(this); } } #endregion #region constructor bind cache private readonly Dictionary constructorBindCache = new(); internal void CacheConstructorBindResult(BindSignature signature, ConstructorInfo result) { constructorBindCache[signature] = result; } internal bool TryGetCachedConstructorBindResult(BindSignature signature, out ConstructorInfo result) { return constructorBindCache.TryGetValue(signature, out result); } private void ClearConstructorBindCache() { constructorBindCache.Clear(); } #endregion #region method bind cache private readonly Dictionary methodBindCache = new(); internal void CacheMethodBindResult(BindSignature signature, object result) { methodBindCache[signature] = result; } internal bool TryGetCachedMethodBindResult(BindSignature signature, out object result) { return methodBindCache.TryGetValue(signature, out result); } private void ClearMethodBindCache() { methodBindCache.Clear(); } #endregion #region property bind cache private readonly Dictionary propertyGetBindCache = new(); private readonly Dictionary propertySetBindCache = new(); internal void CachePropertyGetBindResult(BindSignature signature, MemberInfo property) { propertyGetBindCache[signature] = property; } internal bool TryGetCachedPropertyGetBindResult(BindSignature signature, out MemberInfo property) { return propertyGetBindCache.TryGetValue(signature, out property); } internal void CachePropertySetBindResult(BindSignature signature, MemberInfo property) { propertySetBindCache[signature] = property; } internal bool TryGetCachedPropertySetBindResult(BindSignature signature, out MemberInfo property) { return propertySetBindCache.TryGetValue(signature, out property); } private void ClearPropertyBindCache() { propertyGetBindCache.Clear(); propertySetBindCache.Clear(); } #endregion #region host item cache private readonly ConditionalWeakTable>> hostObjectHostItemCache = new(); private readonly ConditionalWeakTable>> hostTypeHostItemCache = new(); internal HostItem GetOrCreateHostItem(object target, Type type, bool isCanonicalRef, HostItemFlags flags, HostItem.CreateFunc createHostItem) { return GetOrCreateHostItemForHostObject(null, target, type, isCanonicalRef, flags, createHostItem); } internal HostItem GetOrCreateHostItem(HostTarget target, HostItemFlags flags, HostItem.CreateFunc createHostItem) { if (target is HostObject hostObject) { return GetOrCreateHostItemForHostObject(hostObject, hostObject.Target, hostObject.Type, true, flags, createHostItem); } if (target is HostType hostType) { return GetOrCreateHostItemForHostType(hostType, flags, createHostItem); } if (target is HostMethod hostMethod) { return GetOrCreateHostItemForHostObject(hostMethod, hostMethod, hostMethod.Type, true, flags, createHostItem); } if (target is HostVariable hostVariable) { return GetOrCreateHostItemForHostObject(hostVariable, hostVariable, hostVariable.Type, true, flags, createHostItem); } if (target is HostIndexedProperty hostIndexedProperty) { return GetOrCreateHostItemForHostObject(hostIndexedProperty, hostIndexedProperty, hostIndexedProperty.Type, true, flags, createHostItem); } return CreateHostItem(target, flags, createHostItem, null); } private HostItem GetOrCreateHostItemForHostObject(HostTarget hostTarget, object target, Type type, bool isCanonicalRef, HostItemFlags flags, HostItem.CreateFunc createHostItem) { var cacheEntry = hostObjectHostItemCache.GetOrCreateValue(target ?? nullHostObjectProxy); List> activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { if (!weakRef.TryGetTarget(out var hostItem)) { staleWeakRefCount++; } else { if ((hostItem.Target.Type == type) && (hostItem.Flags == flags)) { return hostItem; } if (activeWeakRefs is null) { activeWeakRefs = new List>(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs is not null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } return CreateHostItem(hostTarget ?? HostObject.Wrap(target, type, isCanonicalRef), flags, createHostItem, cacheEntry); } private HostItem GetOrCreateHostItemForHostType(HostType hostType, HostItemFlags flags, HostItem.CreateFunc createHostItem) { if (hostType.Types.Length != 1) { return CreateHostItem(hostType, flags, createHostItem, null); } var cacheEntry = hostTypeHostItemCache.GetOrCreateValue(hostType.Types[0]); List> activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { if (!weakRef.TryGetTarget(out var hostItem)) { staleWeakRefCount++; } else { if (hostItem.Flags == flags) { return hostItem; } if (activeWeakRefs is null) { activeWeakRefs = new List>(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs is not null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } return CreateHostItem(hostType, flags, createHostItem, cacheEntry); } private HostItem CreateHostItem(HostTarget hostTarget, HostItemFlags flags, HostItem.CreateFunc createHostItem, List> cacheEntry) { var newHostItem = createHostItem(this, hostTarget, flags); if (cacheEntry is not null) { cacheEntry.Add(new WeakReference(newHostItem)); } if (hostTarget.Target is IScriptableObject scriptableObject) { scriptableObject.OnExposedToScriptCode(this); } return newHostItem; } #endregion #region host item collateral internal abstract HostItemCollateral HostItemCollateral { get; } #endregion #region shared host target member data internal readonly HostTargetMemberData SharedHostMethodMemberData = new(); internal readonly HostTargetMemberData SharedHostIndexedPropertyMemberData = new(); internal readonly HostTargetMemberData SharedScriptMethodMemberData = new(); private readonly ConditionalWeakTable>> sharedHostObjectMemberDataCache = new(); internal HostTargetMemberData GetSharedHostObjectMemberData(HostObject target, CustomAttributeLoader targetCustomAttributeLoader, Type targetAccessContext, ScriptAccess targetDefaultAccess, HostTargetFlags targetFlags) { var cacheEntry = sharedHostObjectMemberDataCache.GetOrCreateValue(target.Type); List> activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { if (!weakRef.TryGetTarget(out var memberData)) { staleWeakRefCount++; } else { if ((memberData.CustomAttributeLoader == targetCustomAttributeLoader) && (memberData.AccessContext == targetAccessContext) && (memberData.DefaultAccess == targetDefaultAccess) && (memberData.TargetFlags == targetFlags)) { return memberData; } if (activeWeakRefs is null) { activeWeakRefs = new List>(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs is not null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } var newMemberData = new HostTargetMemberDataWithContext(targetCustomAttributeLoader, targetAccessContext, targetDefaultAccess, targetFlags); cacheEntry.Add(new WeakReference(newMemberData)); return newMemberData; } #endregion #region event connections private readonly EventConnectionMap eventConnectionMap = new(); internal EventConnection CreateEventConnection(Type handlerType, object source, EventInfo eventInfo, Delegate handler) { return eventConnectionMap.Create(this, handlerType, source, eventInfo, handler); } internal EventConnection CreateEventConnection(object source, EventInfo eventInfo, Delegate handler) { return eventConnectionMap.Create(this, source, eventInfo, handler); } internal void BreakEventConnection(EventConnection connection) { eventConnectionMap.Break(connection); } private void BreakAllEventConnections() { eventConnectionMap.Dispose(); } #endregion #region disposal / finalization /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the script engine and optionally releases the managed resources. /// /// True to release both managed and unmanaged resources; false to release only unmanaged resources. /// /// This method is called by the public method and the /// Finalize method. invokes the /// protected Dispose(Boolean) method with the /// parameter set to true. Finalize invokes /// Dispose(Boolean) with set to false. /// protected virtual void Dispose(bool disposing) { if (disposing) { BreakAllEventConnections(); } } /// /// Releases unmanaged resources and performs other cleanup operations before the script engine is reclaimed by garbage collection. /// /// /// This method overrides . Application code should not /// call this method; an object's Finalize() method is automatically invoked during /// garbage collection, unless finalization by the garbage collector has been disabled by a /// call to . /// ~ScriptEngine() { Dispose(false); } #endregion #region IHostContext implementation ScriptEngine IHostContext.Engine => this; #endregion #region Nested type: ScriptFrame internal sealed class ScriptFrame { public Exception HostException { get; set; } public IScriptEngineException ScriptError { get; set; } public IScriptEngineException PendingScriptError { get; set; } public bool InterruptRequested { get; set; } } #endregion #region Nested type: EventConnectionMap private sealed class EventConnectionMap : IDisposable { private readonly HashSet map = new(); private readonly InterlockedOneWayFlag disposedFlag = new(); internal EventConnection Create(ScriptEngine engine, Type handlerType, object source, EventInfo eventInfo, Delegate handler) { var connection = (EventConnection)typeof(EventConnection<>).MakeGenericType(handlerType).CreateInstance(BindingFlags.NonPublic, engine, source, eventInfo, handler); Add(connection); return connection; } internal EventConnection Create(ScriptEngine engine, object source, EventInfo eventInfo, Delegate handler) { var connection = new EventConnection(engine, source, eventInfo, handler); Add(connection); return connection; } private void Add(EventConnection connection) { if (!disposedFlag.IsSet) { lock (map) { map.Add(connection); } } } internal void Break(EventConnection connection) { var mustBreak = true; if (!disposedFlag.IsSet) { lock (map) { mustBreak = map.Remove(connection); } } if (mustBreak) { connection.Break(); } } public void Dispose() { if (disposedFlag.Set()) { var connections = new List(); lock (map) { connections.AddRange(map); } connections.ForEach(connection => connection.Break()); } } } #endregion } }