Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
ClassOps.cs376 linesDownload Raw Back to Operations
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Diagnostics.CodeAnalysis;6using System.Linq;7using System.Management.Automation.Language;8using System.Management.Automation.Runspaces;9using System.Reflection;10using System.Reflection.Emit;11using System.Runtime.CompilerServices;12using System.Threading;13 14// These APIs are not part of the public contract.15// They are implementation details and intended to be called from generated assemblies for PS classes.16//17// Because they are called from other assemblies, we have to make them public.18// We put them in Internal namespace to emphasise that despite the fact that they are public, it's not part of API contract.19 20namespace System.Management.Automation.Internal21{22    /// <summary>23    /// Every Runspace in one process contains SessionStateInternal per module (module SessionState).24    /// Every RuntimeType is associated to only one SessionState in the Runspace, which creates it:25    /// it's either global state or a module state.26    /// In the former case, module can be imported from the different runspaces in the same process.27    /// And so runspaces will share RuntimeType. But in every runspace, Type is associated with just one SessionState.28    /// We want type methods to be able access $script: variables and module-specific methods.29    /// To achieve it, we preserve reference to SessionState that creates type in the private field 'SessionStateFieldName'.30    /// Later, we use it to call scriptBlocks captured in ScriptBlockMemberMethodWrapper with the right sessionState.31    /// </summary>32    public class SessionStateKeeper33    {34        // We use ConditionalWeakTable, because if GC already collect Runspace, then there35        // is no way to call a ctor or a static method on the type in this Runspace.36        private readonly ConditionalWeakTable<Runspace, SessionStateInternal> _stateMap;37 38        internal SessionStateKeeper()39        {40            _stateMap = new ConditionalWeakTable<Runspace, SessionStateInternal>();41        }42 43        internal void RegisterRunspace()44        {45            SessionStateInternal sessionStateInMap = null;46            Runspace runspaceToUse = Runspace.DefaultRunspace;47            SessionStateInternal sessionStateToUse = runspaceToUse.ExecutionContext.EngineSessionState;48 49            // Different threads will operate on different key/value pairs (default-runspace/session-state pairs),50            // and a ConditionalWeakTable itself is thread safe, so there won't be race condition here.51            if (!_stateMap.TryGetValue(runspaceToUse, out sessionStateInMap))52            {53                // If the key doesn't exist yet, add it54                _stateMap.Add(runspaceToUse, sessionStateToUse);55            }56            else if (sessionStateInMap != sessionStateToUse)57            {58                // If the key exists but the corresponding value is not what we should use, then remove the key/value pair and add the new pair.59                // This could happen when a powershell class is defined in a module and the module gets reloaded. In such case, the same TypeDefinitionAst60                // instance will get reused, but should be associated with the SessionState from the new module, instead of the one from the old module.61                _stateMap.AddOrUpdate(runspaceToUse, sessionStateToUse);62            }63            // If the key exists and the corresponding value is the one we should use, then do nothing.64        }65 66        /// <summary>67        /// This method should be called only from68        ///  - generated ctors for PowerShell classes, AND69        ///  - ScriptBlockMemberMethodWrapper when invoking static methods of PowerShell classes.70        /// It's not intended to be a public API, but because we generate type in a different assembly it has to be public.71        /// Return type should be SessionStateInternal, but it violates accessibility consistency, so we use object.72        /// </summary>73        /// <remarks>74        /// By default, PowerShell class instantiation usually happens in the same Runspace where the class is defined. In75        /// that case, the created instance will be bound to the session state used to define that class in the Runspace.76        /// However, if the instantiation happens in a different Runspace where the class is not defined, or it happens on77        /// a thread without a default Runspace, then the created instance won't be bound to any session state.78        /// </remarks>79        /// <returns>SessionStateInternal.</returns>80        public object GetSessionState()81        {82            SessionStateInternal ss = null;83 84            // DefaultRunspace could be null when we reach here. For example, create instance of85            // a PowerShell class by using reflection on a thread without DefaultRunspace.86            // Make sure we call 'TryGetValue' with a non-null key, otherwise ArgumentNullException will be thrown.87            Runspace defaultRunspace = Runspace.DefaultRunspace;88            if (defaultRunspace != null)89            {90                _stateMap.TryGetValue(defaultRunspace, out ss);91            }92 93            return ss;94        }95    }96 97    /// <summary/>98    public class ScriptBlockMemberMethodWrapper99    {100        /// <summary>Used in codegen</summary>101        public static readonly object[] _emptyArgumentArray = Array.Empty<object>(); // See TypeDefiner.DefineTypeHelper.DefineMethodBody102 103        /// <summary>104        /// Indicate the wrapper is for a static member method.105        /// </summary>106        private readonly bool _isStatic;107 108        /// <summary>109        /// The SessionStateKeeper associated with the helper type generated from PowerShell class.110        /// We query it for the SessionState to run static method in.111        /// </summary>112        private readonly SessionStateKeeper _sessionStateKeeper;113 114        /// <summary>115        /// We use WeakReference object to point to the default SessionState because if GC already collect the SessionState,116        /// or the Runspace it chains to is closed and disposed, then we cannot run the static method there anyways.117        /// </summary>118        /// <remarks>119        /// The default SessionState is used only if a static method is called from a Runspace where the PowerShell class is120        /// never defined, or is called on a thread without a default Runspace. Usage like those should be rare.121        /// </remarks>122        private readonly WeakReference<SessionStateInternal> _defaultSessionStateToUse;123 124        /// <summary>125        /// The body AST of the member method.126        /// </summary>127        private readonly IParameterMetadataProvider _ast;128 129        /// <summary>130        /// We use _scriptBlock instance to provide the shared CompiledScriptBlockData.131        /// </summary>132        private readonly Lazy<ScriptBlock> _scriptBlock;133 134        /// <summary>135        /// We use ThreadLocal boundScriptBlock to allow multi-thread execution of member methods.136        /// </summary>137        private readonly ThreadLocal<ScriptBlock> _boundScriptBlock;138 139        /// <summary>140        /// Constructor to be called when the wrapper is for a static member method.141        /// </summary>142        internal ScriptBlockMemberMethodWrapper(IParameterMetadataProvider ast, SessionStateKeeper sessionStateKeeper)143            : this(ast)144        {145            _isStatic = true;146            _sessionStateKeeper = sessionStateKeeper;147            _defaultSessionStateToUse = new WeakReference<SessionStateInternal>(null);148        }149 150        /// <summary>151        /// Constructor to be called when the wrapper is for an instance member method.152        /// </summary>153        internal ScriptBlockMemberMethodWrapper(IParameterMetadataProvider ast)154        {155            _ast = ast;156            // This 'Lazy<T>' constructor ensures that only a single thread can initialize the instance in a thread-safe manner.157            _scriptBlock = new Lazy<ScriptBlock>(() => new ScriptBlock(_ast, isFilter: false));158            _boundScriptBlock = new ThreadLocal<ScriptBlock>(() => _scriptBlock.Value.Clone());159        }160 161        /// <summary>162        /// Initialization happens when the script that defines PowerShell class is executed.163        /// This initialization is required only if this wrapper is for a static method.164        /// </summary>165        /// <remarks>166        /// When the same script file gets executed multiple times, the .NET type generated from the PowerShell class167        /// defined in the file will be shared in those executions, and thus this method will be called multiple times168        /// possibly in the contexts of different Runspace/SessionState.169        ///170        /// We always use the SessionState from the most recent execution as the default SessionState, so be noted that171        /// the default SessionState may change over time.172        ///173        /// This should be OK because the common usage is to run the static method in the same Runspace where the class174        /// is declared, and thus we can always get the correct SessionState to use by querying the 'SessionStateKeeper'.175        /// The default SessionState is used only if a static method is called from a Runspace where the class is never176        /// defined, or is called on a thread without a default Runspace.177        /// </remarks>178        internal void InitAtRuntime()179        {180            if (_isStatic)181            {182                // WeakReference<T>'s instance methods are not thread-safe, so we need the lock to guarantee183                // 'SetTarget' and 'TryGetTarget' are not called by multiple threads at the same time.184                lock (_defaultSessionStateToUse)185                {186                    var context = Runspace.DefaultRunspace.ExecutionContext;187                    _defaultSessionStateToUse.SetTarget(context.EngineSessionState);188                }189            }190        }191 192        /// <summary>193        /// Set the SessionState of the script block appropriately.194        /// </summary>195        private void PrepareScriptBlockToInvoke(object instance, object sessionStateInternal)196        {197            SessionStateInternal sessionStateToUse = null;198            if (instance != null)199            {200                // Use the SessionState passed in, which is the one associated with the instance.201                sessionStateToUse = (SessionStateInternal)sessionStateInternal;202            }203            else204            {205                // For static method, it's a little complex.206                // - Check if the current default runspace is registered with the SessionStateKeeper. If so, use the registered SessionState.207                // - Otherwise, check if default SessionState is still alive. If so, use the default SessionState.208                // - Otherwise, the 'SessionStateInternal' property will be set to null, and thus the default runspace of the current thread will be used.209                //              If the current thread doesn't have a default Runspace, then an InvalidOperationException will be thrown when invoking the210                //              script block, which is expected.211                sessionStateToUse = (SessionStateInternal)_sessionStateKeeper.GetSessionState();212                if (sessionStateToUse == null)213                {214                    lock (_defaultSessionStateToUse)215                    {216                        _defaultSessionStateToUse.TryGetTarget(out sessionStateToUse);217                    }218                }219            }220 221            _boundScriptBlock.Value.SessionStateInternal = sessionStateToUse;222        }223 224        /// <summary>225        /// </summary>226        /// <param name="instance">Target object or null for static call.</param>227        /// <param name="sessionStateInternal">SessionStateInternal from private field of instance or null for static call.</param>228        /// <param name="args"></param>229        public void InvokeHelper(object instance, object sessionStateInternal, object[] args)230        {231            try232            {233                PrepareScriptBlockToInvoke(instance, sessionStateInternal);234                _boundScriptBlock.Value.InvokeAsMemberFunction(instance, args);235            }236            finally237            {238                // '_boundScriptBlock.Value' for a thread will live until239                //  - the thread is gone, OR240                //  - the dyanmic assembly holding this wrapper instance is GC collected.241                // We don't hold on the SessionState object, so that GC can collect it as appropriate.242                _boundScriptBlock.Value.SessionStateInternal = null;243            }244        }245 246        /// <summary>247        /// </summary>248        /// <typeparam name="T"></typeparam>249        /// <param name="instance">Target object or null for static call.</param>250        /// <param name="sessionStateInternal">SessionStateInternal from private field of instance or null for static call.</param>251        /// <param name="args"></param>252        /// <returns></returns>253        public T InvokeHelperT<T>(object instance, object sessionStateInternal, object[] args)254        {255            try256            {257                PrepareScriptBlockToInvoke(instance, sessionStateInternal);258                return _boundScriptBlock.Value.InvokeAsMemberFunctionT<T>(instance, args);259            }260            finally261            {262                // '_boundScriptBlock.Value' for a thread will live until263                //  - the thread is gone, OR264                //  - the dyanmic assembly holding this wrapper instance is GC collected.265                // We don't hold on the SessionState object, so that GC can collect it as appropriate.266                _boundScriptBlock.Value.SessionStateInternal = null;267            }268        }269    }270 271    /// <summary>272    /// Support methods for PowerShell classes.273    /// </summary>274    public static class ClassOps275    {276        /// <summary>277        /// This method calls all Validate attributes for the property to validate value.278        /// Called from class property setters with ValidateArgumentsAttribute attributes.279        /// </summary>280        /// <param name="type"></param>281        /// <param name="propertyName"></param>282        /// <param name="value"></param>283        public static void ValidateSetProperty(Type type, string propertyName, object value)284        {285            var validateAttributes = type.GetProperty(propertyName).GetCustomAttributes<ValidateArgumentsAttribute>();286            var executionContext = LocalPipeline.GetExecutionContextFromTLS();287            var engineIntrinsics = executionContext?.EngineIntrinsics;288            foreach (var validateAttribute in validateAttributes)289            {290                validateAttribute.InternalValidate(value, engineIntrinsics);291            }292        }293 294        /// <summary>295        /// Performs base ctor call as a method call.296        /// </summary>297        /// <param name="target">Object for invocation.</param>298        /// <param name="ci">Ctor info for invocation.</param>299        /// <param name="args">Arguments for invocation.</param>300        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]301        public static void CallBaseCtor(object target, ConstructorInfo ci, object[] args)302        {303            ci.Invoke(target, args);304        }305 306        /// <summary>307        /// Performs non-virtual method call with return value. Main usage: base class method call inside subclass method.308        /// </summary>309        /// <param name="target">Object for invocation.</param>310        /// <param name="mi">Method info for invocation.</param>311        /// <param name="args">Arguments for invocation.</param>312        public static object CallMethodNonVirtually(object target, MethodInfo mi, object[] args)313        {314            return CallMethodNonVirtuallyImpl(target, mi, args);315        }316 317        /// <summary>318        /// Performs non-virtual void method call. Main usage: base class method call inside subclass method.319        /// </summary>320        /// <param name="target">Object for invocation.</param>321        /// <param name="mi">Method info for invocation.</param>322        /// <param name="args">Arguments for invocation.</param>323        public static void CallVoidMethodNonVirtually(object target, MethodInfo mi, object[] args)324        {325            CallMethodNonVirtuallyImpl(target, mi, args);326        }327 328        /// <summary>329        /// A cache for the DynamicMethod objects that call to base method non-virtually.330        /// The cache can clean up the outdated WeakReference entries by itself.331        /// </summary>332        private static readonly ConditionalWeakTable<MethodInfo, DynamicMethod> s_nonVirtualCallCache =333            new ConditionalWeakTable<MethodInfo, DynamicMethod>();334 335        /// <summary>336        /// Implementation of non-virtual method call.337        /// </summary>338        /// <param name="target">Object for invocation.</param>339        /// <param name="mi">Method info for invocation.</param>340        /// <param name="args">Arguments for invocation.</param>341        private static object CallMethodNonVirtuallyImpl(object target, MethodInfo mi, object[] args)342        {343            DynamicMethod dm = s_nonVirtualCallCache.GetValue(mi, CreateDynamicMethod);344 345            // The target object will be passed to the hidden parameter 'this' of the instance method346            var newArgs = new List<object>(args.Length + 1) { target };347            newArgs.AddRange(args);348 349            return dm.Invoke(null, newArgs.ToArray());350        }351 352        /// <summary>353        /// Help method to create the DynamicMethod for calling base method non-virtually.354        /// </summary>355        private static DynamicMethod CreateDynamicMethod(MethodInfo mi)356        {357            // Pass in the declaring type because instance method has a hidden parameter 'this' as the first parameter.358            var paramTypes = new List<Type> { mi.DeclaringType };359            paramTypes.AddRange(mi.GetParameters().Select(static x => x.ParameterType));360 361            var dm = new DynamicMethod("PSNonVirtualCall_" + mi.Name, mi.ReturnType, paramTypes.ToArray(), mi.DeclaringType);362            ILGenerator il = dm.GetILGenerator();363            for (int i = 0; i < paramTypes.Count; i++)364            {365                il.Emit(OpCodes.Ldarg, i);366            }367 368            il.Emit(OpCodes.Tailcall);369            il.EmitCall(OpCodes.Call, mi, null);370            il.Emit(OpCodes.Ret);371 372            return dm;373        }374    }375}376