PowerShell/src/System.Management.Automation/engine/parser/Compiler.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

7020 lines
330 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Internal;
using System.Management.Automation.Interpreter;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.PowerShell.Commands;
namespace System.Management.Automation.Language
{
using KeyValuePair = Tuple<ExpressionAst, StatementAst>;
using IfClause = Tuple<PipelineBaseAst, StatementBlockAst>;
internal static class CachedReflectionInfo
{
// ReSharper disable InconsistentNaming
internal const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
internal const BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.NonPublic;
internal const BindingFlags StaticPublicFlags = BindingFlags.Static | BindingFlags.Public;
internal const BindingFlags InstancePublicFlags = BindingFlags.Instance | BindingFlags.Public;
internal static readonly ConstructorInfo ObjectList_ctor =
typeof(List<object>).GetConstructor(Type.EmptyTypes);
internal static readonly MethodInfo ObjectList_ToArray =
typeof(List<object>).GetMethod(nameof(List<object>.ToArray), Type.EmptyTypes);
internal static readonly MethodInfo ArrayOps_GetMDArrayValue =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValue), StaticFlags);
internal static readonly MethodInfo ArrayOps_GetMDArrayValueOrSlice =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValueOrSlice), StaticFlags);
internal static readonly MethodInfo ArrayOps_GetNonIndexable =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetNonIndexable), StaticFlags);
internal static readonly MethodInfo ArrayOps_IndexStringMessage =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.IndexStringMessage), StaticFlags);
internal static readonly MethodInfo ArrayOps_Multiply =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.Multiply), StaticFlags);
internal static readonly MethodInfo ArrayOps_SetMDArrayValue =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.SetMDArrayValue), StaticFlags);
internal static readonly MethodInfo ArrayOps_SlicingIndex =
typeof(ArrayOps).GetMethod(nameof(ArrayOps.SlicingIndex), StaticFlags);
internal static readonly ConstructorInfo BreakException_ctor =
typeof(BreakException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(string) }, null);
internal static readonly MethodInfo CharOps_CompareIeq =
typeof(CharOps).GetMethod(nameof(CharOps.CompareIeq), StaticFlags);
internal static readonly MethodInfo CharOps_CompareIne =
typeof(CharOps).GetMethod(nameof(CharOps.CompareIne), StaticFlags);
internal static readonly MethodInfo CharOps_CompareStringIeq =
typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIeq), StaticFlags);
internal static readonly MethodInfo CharOps_CompareStringIne =
typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIne), StaticFlags);
internal static readonly MethodInfo CommandParameterInternal_CreateArgument =
typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateArgument), StaticFlags);
internal static readonly MethodInfo CommandParameterInternal_CreateParameter =
typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameter), StaticFlags);
internal static readonly MethodInfo CommandParameterInternal_CreateParameterWithArgument =
typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameterWithArgument), StaticFlags);
internal static readonly MethodInfo CommandRedirection_UnbindForExpression =
typeof(CommandRedirection).GetMethod(nameof(CommandRedirection.UnbindForExpression), InstanceFlags);
internal static readonly ConstructorInfo ContinueException_ctor =
typeof(ContinueException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(string) }, null);
internal static readonly MethodInfo Convert_ChangeType =
typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type) });
internal static readonly MethodInfo Debugger_EnterScriptFunction =
typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.EnterScriptFunction), InstanceFlags);
internal static readonly MethodInfo Debugger_ExitScriptFunction =
typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.ExitScriptFunction), InstanceFlags);
internal static readonly MethodInfo Debugger_OnSequencePointHit =
typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.OnSequencePointHit), InstanceFlags);
internal static readonly MethodInfo EnumerableOps_AddEnumerable =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddEnumerable), StaticFlags);
internal static readonly MethodInfo EnumerableOps_AddObject =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddObject), StaticFlags);
internal static readonly MethodInfo EnumerableOps_Compare =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Compare), StaticFlags);
internal static readonly MethodInfo EnumerableOps_GetEnumerator =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetEnumerator), StaticFlags);
internal static readonly MethodInfo EnumerableOps_GetCOMEnumerator =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetCOMEnumerator), StaticFlags);
internal static readonly MethodInfo EnumerableOps_GetGenericEnumerator =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetGenericEnumerator), StaticFlags);
internal static readonly MethodInfo EnumerableOps_GetSlice =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetSlice), StaticFlags);
internal static readonly MethodInfo EnumerableOps_MethodInvoker =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.MethodInvoker), StaticFlags);
internal static readonly MethodInfo EnumerableOps_Where =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Where), StaticFlags);
internal static readonly MethodInfo EnumerableOps_ForEach =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ForEach), StaticFlags);
internal static readonly MethodInfo EnumerableOps_Multiply =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Multiply), StaticFlags);
internal static readonly MethodInfo EnumerableOps_PropertyGetter =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.PropertyGetter), StaticFlags);
internal static readonly MethodInfo EnumerableOps_SlicingIndex =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.SlicingIndex), StaticFlags);
internal static readonly MethodInfo EnumerableOps_ToArray =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ToArray), StaticFlags);
internal static readonly MethodInfo EnumerableOps_WriteEnumerableToPipe =
typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.WriteEnumerableToPipe), StaticFlags);
internal static readonly ConstructorInfo ErrorRecord__ctor =
typeof(ErrorRecord).GetConstructor(
InstanceFlags | BindingFlags.Public,
null,
CallingConventions.Standard,
new Type[] { typeof(ErrorRecord), typeof(RuntimeException) },
null);
internal static readonly PropertyInfo Exception_Message =
typeof(Exception).GetProperty(nameof(Exception.Message));
internal static readonly MethodInfo ExceptionHandlingOps_CheckActionPreference =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.CheckActionPreference), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_ConvertToArgumentConversionException =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToArgumentConversionException), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_ConvertToException =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToException), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_ConvertToMethodInvocationException =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToMethodInvocationException), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_FindMatchingHandler =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.FindMatchingHandler), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_RestoreStoppingPipeline =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.RestoreStoppingPipeline), StaticFlags);
internal static readonly MethodInfo ExceptionHandlingOps_SuspendStoppingPipeline =
typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.SuspendStoppingPipeline), StaticFlags);
internal static readonly PropertyInfo ExecutionContext_CurrentExceptionBeingHandled =
typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.CurrentExceptionBeingHandled), InstanceFlags);
internal static readonly FieldInfo ExecutionContext_Debugger =
typeof(ExecutionContext).GetField(nameof(ExecutionContext._debugger), InstanceFlags);
internal static readonly FieldInfo ExecutionContext_DebuggingMode =
typeof(ExecutionContext).GetField(nameof(ExecutionContext._debuggingMode), InstanceFlags);
internal static readonly PropertyInfo ExecutionContext_ExceptionHandlerInEnclosingStatementBlock =
typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.PropagateExceptionsToEnclosingStatementBlock), InstanceFlags);
internal static readonly MethodInfo ExecutionContext_IsStrictVersion =
typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.IsStrictVersion), StaticFlags);
internal static readonly PropertyInfo ExecutionContext_QuestionMarkVariableValue =
typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.QuestionMarkVariableValue), InstanceFlags);
internal static readonly PropertyInfo ExecutionContext_LanguageMode =
typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.LanguageMode), InstanceFlags);
internal static readonly PropertyInfo ExecutionContext_EngineIntrinsics =
typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.EngineIntrinsics), InstanceFlags);
internal static readonly MethodInfo FileRedirection_BindForExpression =
typeof(FileRedirection).GetMethod(nameof(FileRedirection.BindForExpression), InstanceFlags);
internal static readonly MethodInfo FileRedirection_CallDoCompleteForExpression =
typeof(FileRedirection).GetMethod(nameof(FileRedirection.CallDoCompleteForExpression), InstanceFlags);
internal static readonly ConstructorInfo FileRedirection_ctor =
typeof(FileRedirection).GetConstructor(
InstanceFlags,
null,
CallingConventions.Standard,
new Type[] { typeof(RedirectionStream), typeof(bool), typeof(string) },
null);
internal static readonly MethodInfo FileRedirection_Dispose =
typeof(FileRedirection).GetMethod(nameof(FileRedirection.Dispose));
internal static readonly FieldInfo FunctionContext__currentSequencePointIndex =
typeof(FunctionContext).GetField(nameof(FunctionContext._currentSequencePointIndex), InstanceFlags);
internal static readonly FieldInfo FunctionContext__executionContext =
typeof(FunctionContext).GetField(nameof(FunctionContext._executionContext), InstanceFlags);
internal static readonly FieldInfo FunctionContext__functionName =
typeof(FunctionContext).GetField(nameof(FunctionContext._functionName), InstanceFlags);
internal static readonly FieldInfo FunctionContext__localsTuple =
typeof(FunctionContext).GetField(nameof(FunctionContext._localsTuple), InstanceFlags);
internal static readonly FieldInfo FunctionContext__outputPipe =
typeof(FunctionContext).GetField(nameof(FunctionContext._outputPipe), InstanceFlags);
internal static readonly MethodInfo FunctionContext_PopTrapHandlers =
typeof(FunctionContext).GetMethod(nameof(FunctionContext.PopTrapHandlers), InstanceFlags);
internal static readonly MethodInfo FunctionContext_PushTrapHandlers =
typeof(FunctionContext).GetMethod(nameof(FunctionContext.PushTrapHandlers), InstanceFlags);
internal static readonly MethodInfo FunctionOps_DefineFunction =
typeof(FunctionOps).GetMethod(nameof(FunctionOps.DefineFunction), StaticFlags);
internal static readonly ConstructorInfo Hashtable_ctor =
typeof(Hashtable).GetConstructor(
BindingFlags.Instance | BindingFlags.Public,
null,
CallingConventions.Standard,
new Type[] { typeof(int), typeof(IEqualityComparer) },
null);
internal static readonly MethodInfo HashtableOps_Add =
typeof(HashtableOps).GetMethod(nameof(HashtableOps.Add), StaticFlags);
internal static readonly MethodInfo HashtableOps_AddKeyValuePair =
typeof(HashtableOps).GetMethod(nameof(HashtableOps.AddKeyValuePair), StaticFlags);
internal static readonly PropertyInfo ICollection_Count =
typeof(ICollection).GetProperty(nameof(ICollection.Count));
internal static readonly MethodInfo IComparable_CompareTo =
typeof(IComparable).GetMethod(nameof(IComparable.CompareTo));
internal static readonly MethodInfo IDisposable_Dispose =
typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose));
internal static readonly MethodInfo IEnumerable_GetEnumerator =
typeof(IEnumerable).GetMethod(nameof(IEnumerable.GetEnumerator));
internal static readonly PropertyInfo IEnumerator_Current =
typeof(IEnumerator).GetProperty(nameof(IEnumerator.Current));
internal static readonly MethodInfo IEnumerator_MoveNext =
typeof(IEnumerator).GetMethod(nameof(IEnumerator.MoveNext));
internal static readonly MethodInfo IList_get_Item =
typeof(IList).GetMethod("get_Item");
internal static readonly MethodInfo InterpreterError_NewInterpreterException =
typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterException), StaticFlags);
internal static readonly MethodInfo InterpreterError_NewInterpreterExceptionWithInnerException =
typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterExceptionWithInnerException), StaticFlags);
internal static readonly MethodInfo LanguagePrimitives_GetInvalidCastMessages =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.GetInvalidCastMessages), StaticFlags);
internal static readonly MethodInfo LanguagePrimitives_IsNull =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.IsNull), StaticFlags);
internal static readonly MethodInfo LanguagePrimitives_ThrowInvalidCastException =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.ThrowInvalidCastException), StaticFlags);
internal static readonly MethodInfo LocalPipeline_GetExecutionContextFromTLS =
typeof(LocalPipeline).GetMethod(nameof(LocalPipeline.GetExecutionContextFromTLS), StaticFlags);
internal static readonly MethodInfo LoopFlowException_MatchLabel =
typeof(LoopFlowException).GetMethod(nameof(LoopFlowException.MatchLabel), InstanceFlags);
internal static readonly MethodInfo MergingRedirection_BindForExpression =
typeof(MergingRedirection).GetMethod(nameof(MergingRedirection.BindForExpression), InstanceFlags);
internal static readonly ConstructorInfo MethodException_ctor =
typeof(MethodException).GetConstructor(
InstanceFlags,
null,
new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
null);
internal static readonly MethodInfo MutableTuple_IsValueSet =
typeof(MutableTuple).GetMethod(nameof(MutableTuple.IsValueSet), InstanceFlags);
internal static readonly MethodInfo Object_Equals =
typeof(object).GetMethod(nameof(object.Equals), new Type[] { typeof(object) });
internal static readonly ConstructorInfo OrderedDictionary_ctor =
typeof(OrderedDictionary).GetConstructor(
BindingFlags.Instance | BindingFlags.Public,
null,
CallingConventions.Standard,
new Type[] { typeof(int), typeof(IEqualityComparer) },
null);
internal static readonly MethodInfo Parser_ScanNumber =
typeof(Parser).GetMethod(nameof(Parser.ScanNumber), StaticFlags);
internal static readonly MethodInfo ParserOps_ContainsOperatorCompiled =
typeof(ParserOps).GetMethod(nameof(ParserOps.ContainsOperatorCompiled), StaticFlags);
internal static readonly MethodInfo ParserOps_ImplicitOp =
typeof(ParserOps).GetMethod(nameof(ParserOps.ImplicitOp), StaticFlags);
internal static readonly MethodInfo ParserOps_JoinOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.JoinOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_LikeOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.LikeOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_MatchOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.MatchOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_RangeOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.RangeOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_GetRangeEnumerator =
typeof(ParserOps).GetMethod(nameof(ParserOps.GetRangeEnumerator), StaticFlags);
internal static readonly MethodInfo ParserOps_ReplaceOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.ReplaceOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_SplitOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.SplitOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_UnaryJoinOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.UnaryJoinOperator), StaticFlags);
internal static readonly MethodInfo ParserOps_UnarySplitOperator =
typeof(ParserOps).GetMethod(nameof(ParserOps.UnarySplitOperator), StaticFlags);
internal static readonly ConstructorInfo Pipe_ctor =
typeof(Pipe).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(List<object>) }, null);
internal static readonly MethodInfo Pipe_Add =
typeof(Pipe).GetMethod(nameof(Pipe.Add), InstanceFlags);
internal static readonly MethodInfo Pipe_SetVariableListForTemporaryPipe =
typeof(Pipe).GetMethod(nameof(Pipe.SetVariableListForTemporaryPipe), InstanceFlags);
internal static readonly MethodInfo PipelineOps_CheckAutomationNullInCommandArgument =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgument), StaticFlags);
internal static readonly MethodInfo PipelineOps_CheckAutomationNullInCommandArgumentArray =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgumentArray), StaticFlags);
internal static readonly MethodInfo PipelineOps_CheckForInterrupts =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckForInterrupts), StaticFlags);
internal static readonly MethodInfo PipelineOps_GetExitException =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.GetExitException), StaticFlags);
internal static readonly MethodInfo PipelineOps_FlushPipe =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.FlushPipe), StaticFlags);
internal static readonly MethodInfo PipelineOps_InvokePipeline =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipeline), StaticFlags);
internal static readonly MethodInfo PipelineOps_InvokePipelineInBackground =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipelineInBackground), StaticFlags);
internal static readonly MethodInfo PipelineOps_Nop =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.Nop), StaticFlags);
internal static readonly MethodInfo PipelineOps_PipelineResult =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.PipelineResult), StaticFlags);
internal static readonly MethodInfo PipelineOps_ClearPipe =
typeof(PipelineOps).GetMethod(nameof(PipelineOps.ClearPipe), StaticFlags);
internal static readonly MethodInfo PSGetDynamicMemberBinder_GetIDictionaryMember =
typeof(PSGetDynamicMemberBinder).GetMethod(nameof(PSGetDynamicMemberBinder.GetIDictionaryMember), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_CloneMemberInfo =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.CloneMemberInfo), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_GetAdaptedValue =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetAdaptedValue), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_GetTypeTableFromTLS =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetTypeTableFromTLS), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_IsTypeNameSame =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.IsTypeNameSame), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_TryGetGenericDictionaryValue =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetGenericDictionaryValue), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_TryGetInstanceMember =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetInstanceMember), StaticFlags);
internal static readonly MethodInfo PSGetMemberBinder_TryGetIDictionaryValue =
typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetIDictionaryValue), StaticFlags);
internal static readonly MethodInfo PSInvokeMemberBinder_InvokeAdaptedMember =
typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedMember), StaticFlags);
internal static readonly MethodInfo PSInvokeMemberBinder_InvokeAdaptedSetMember =
typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedSetMember), StaticFlags);
internal static readonly MethodInfo PSInvokeMemberBinder_IsHeterogeneousArray =
typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHeterogeneousArray), StaticFlags);
internal static readonly MethodInfo PSInvokeMemberBinder_IsHomogenousArray =
typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHomogenousArray), StaticFlags);
internal static readonly MethodInfo PSInvokeMemberBinder_TryGetInstanceMethod =
typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.TryGetInstanceMethod), StaticFlags);
internal static readonly MethodInfo PSMethodInfo_Invoke =
typeof(PSMethodInfo).GetMethod(nameof(PSMethodInfo.Invoke));
internal static readonly PropertyInfo PSNoteProperty_Value =
typeof(PSNoteProperty).GetProperty(nameof(PSNoteProperty.Value));
internal static readonly MethodInfo PSObject_Base =
typeof(PSObject).GetMethod(nameof(PSObject.Base), StaticFlags);
internal static readonly PropertyInfo PSObject_BaseObject =
typeof(PSObject).GetProperty(nameof(PSObject.BaseObject));
internal static readonly PropertyInfo PSObject_IsDeserialized =
typeof(PSObject).GetProperty(nameof(PSObject.IsDeserialized), InstanceFlags);
internal static readonly MethodInfo PSObject_ToStringParser =
typeof(PSObject).GetMethod(nameof(PSObject.ToStringParser), StaticFlags, null, new[] { typeof(ExecutionContext), typeof(object) }, null);
internal static readonly PropertyInfo PSReference_Value =
typeof(PSReference).GetProperty(nameof(PSReference.Value));
internal static readonly MethodInfo PSScriptMethod_InvokeScript =
typeof(PSScriptMethod).GetMethod(nameof(PSScriptMethod.InvokeScript), StaticFlags);
internal static readonly MethodInfo PSScriptProperty_InvokeGetter =
typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeGetter), InstanceFlags);
internal static readonly MethodInfo PSScriptProperty_InvokeSetter =
typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeSetter), InstanceFlags);
internal static readonly MethodInfo PSSetMemberBinder_SetAdaptedValue =
typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetAdaptedValue), StaticFlags);
internal static readonly MethodInfo PSVariableAssignmentBinder_CopyInstanceMembersOfValueType =
typeof(PSVariableAssignmentBinder).GetMethod(nameof(PSVariableAssignmentBinder.CopyInstanceMembersOfValueType), StaticFlags);
internal static readonly FieldInfo PSVariableAssignmentBinder__mutableValueWithInstanceMemberVersion =
typeof(PSVariableAssignmentBinder).GetField(nameof(PSVariableAssignmentBinder.s_mutableValueWithInstanceMemberVersion), StaticFlags);
internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeNonPublic =
typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeNonPublic), StaticFlags);
internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeByRefLike =
typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeByRefLike), StaticFlags);
internal static readonly MethodInfo PSCreateInstanceBinder_GetTargetTypeName =
typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.GetTargetTypeName), StaticFlags);
internal static readonly MethodInfo ReservedNameMembers_GeneratePSAdaptedMemberSet =
typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSAdaptedMemberSet), StaticFlags);
internal static readonly MethodInfo ReservedNameMembers_GeneratePSBaseMemberSet =
typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSBaseMemberSet), StaticFlags);
internal static readonly MethodInfo ReservedNameMembers_GeneratePSExtendedMemberSet =
typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSExtendedMemberSet), StaticFlags);
internal static readonly MethodInfo ReservedNameMembers_GeneratePSObjectMemberSet =
typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSObjectMemberSet), StaticFlags);
internal static readonly MethodInfo ReservedNameMembers_PSTypeNames =
typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.PSTypeNames));
internal static readonly MethodInfo RestrictedLanguageChecker_CheckDataStatementLanguageModeAtRuntime =
typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementLanguageModeAtRuntime), StaticFlags);
internal static readonly MethodInfo RestrictedLanguageChecker_CheckDataStatementAstAtRuntime =
typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementAstAtRuntime), StaticFlags);
internal static readonly MethodInfo RestrictedLanguageChecker_EnsureUtilityModuleLoaded =
typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.EnsureUtilityModuleLoaded), StaticFlags);
internal static readonly ConstructorInfo ReturnException_ctor =
typeof(ReturnException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(object) }, null);
internal static readonly PropertyInfo RuntimeException_ErrorRecord =
typeof(RuntimeException).GetProperty(nameof(RuntimeException.ErrorRecord));
internal static readonly MethodInfo ScriptBlock_DoInvokeReturnAsIs =
typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.DoInvokeReturnAsIs), InstanceFlags);
internal static readonly MethodInfo ScriptBlock_InvokeAsDelegateHelper =
typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.InvokeAsDelegateHelper), InstanceFlags);
internal static readonly MethodInfo ScriptBlockExpressionWrapper_GetScriptBlock =
typeof(ScriptBlockExpressionWrapper).GetMethod(nameof(ScriptBlockExpressionWrapper.GetScriptBlock), InstanceFlags);
internal static readonly ConstructorInfo SetValueException_ctor =
typeof(SetValueException).GetConstructor(
InstanceFlags,
null,
new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
null);
internal static readonly ConstructorInfo GetValueException_ctor =
typeof(GetValueException).GetConstructor(
InstanceFlags,
null,
new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
null);
internal static readonly ConstructorInfo StreamReader_ctor =
typeof(StreamReader).GetConstructor(new Type[] { typeof(string) });
internal static readonly MethodInfo StreamReader_ReadLine =
typeof(StreamReader).GetMethod(nameof(StreamReader.ReadLine), Type.EmptyTypes);
internal static readonly ConstructorInfo String_ctor_char_int =
typeof(string).GetConstructor(new Type[] { typeof(char), typeof(int) });
internal static readonly MethodInfo String_Concat_String =
typeof(string).GetMethod(
nameof(string.Concat),
StaticPublicFlags, null,
CallingConventions.Standard,
new Type[] { typeof(string), typeof(string) },
null);
internal static readonly MethodInfo String_Equals =
typeof(string).GetMethod(
nameof(string.Equals),
StaticPublicFlags,
null,
CallingConventions.Standard,
new Type[] { typeof(string), typeof(string), typeof(StringComparison) },
null);
internal static readonly MethodInfo StringOps_Compare =
typeof(StringOps).GetMethod(nameof(StringOps.Compare), StaticFlags);
internal static readonly MethodInfo StringOps_Equals =
typeof(StringOps).GetMethod(nameof(StringOps.Equals), StaticFlags);
internal static readonly MethodInfo StringOps_FormatOperator =
typeof(StringOps).GetMethod(nameof(StringOps.FormatOperator), StaticFlags);
internal static readonly MethodInfo StringOps_Multiply =
typeof(StringOps).GetMethod(nameof(StringOps.Multiply), StaticFlags);
internal static readonly MethodInfo SwitchOps_ConditionSatisfiedRegex =
typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedRegex), StaticFlags);
internal static readonly MethodInfo SwitchOps_ConditionSatisfiedWildcard =
typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedWildcard), StaticFlags);
internal static readonly MethodInfo SwitchOps_ResolveFilePath =
typeof(SwitchOps).GetMethod(nameof(SwitchOps.ResolveFilePath), StaticFlags);
internal static readonly MethodInfo TypeOps_AsOperator =
typeof(TypeOps).GetMethod(nameof(TypeOps.AsOperator), StaticFlags);
internal static readonly MethodInfo TypeOps_AddPowerShellTypesToTheScope =
typeof(TypeOps).GetMethod(nameof(TypeOps.AddPowerShellTypesToTheScope), StaticFlags);
internal static readonly MethodInfo TypeOps_InitPowerShellTypesAtRuntime =
typeof(TypeOps).GetMethod(nameof(TypeOps.InitPowerShellTypesAtRuntime), StaticFlags);
internal static readonly MethodInfo TypeOps_SetCurrentTypeResolutionState =
typeof(TypeOps).GetMethod(nameof(TypeOps.SetCurrentTypeResolutionState), StaticFlags);
internal static readonly MethodInfo TypeOps_SetAssemblyDefiningPSTypes =
typeof(TypeOps).GetMethod(nameof(TypeOps.SetAssemblyDefiningPSTypes), StaticFlags);
internal static readonly MethodInfo TypeOps_IsInstance =
typeof(TypeOps).GetMethod(nameof(TypeOps.IsInstance), StaticFlags);
internal static readonly MethodInfo TypeOps_ResolveTypeName =
typeof(TypeOps).GetMethod(nameof(TypeOps.ResolveTypeName), StaticFlags);
internal static readonly MethodInfo VariableOps_GetUsingValue =
typeof(VariableOps).GetMethod(nameof(VariableOps.GetUsingValue), StaticFlags);
internal static readonly MethodInfo VariableOps_GetVariableAsRef =
typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableAsRef), StaticFlags);
internal static readonly MethodInfo VariableOps_GetVariableValue =
typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableValue), StaticFlags);
internal static readonly MethodInfo VariableOps_GetAutomaticVariableValue =
typeof(VariableOps).GetMethod(nameof(VariableOps.GetAutomaticVariableValue), StaticFlags);
internal static readonly MethodInfo VariableOps_SetVariableValue =
typeof(VariableOps).GetMethod(nameof(VariableOps.SetVariableValue), StaticFlags);
internal static readonly MethodInfo Utils_IsComObject =
typeof(Utils).GetMethod(nameof(Utils.IsComObject), StaticFlags);
internal static readonly MethodInfo ClassOps_ValidateSetProperty =
typeof(ClassOps).GetMethod(nameof(ClassOps.ValidateSetProperty), StaticPublicFlags);
internal static readonly MethodInfo ClassOps_CallBaseCtor =
typeof(ClassOps).GetMethod(nameof(ClassOps.CallBaseCtor), StaticPublicFlags);
internal static readonly MethodInfo ClassOps_CallMethodNonVirtually =
typeof(ClassOps).GetMethod(nameof(ClassOps.CallMethodNonVirtually), StaticPublicFlags);
internal static readonly MethodInfo ClassOps_CallVoidMethodNonVirtually =
typeof(ClassOps).GetMethod(nameof(ClassOps.CallVoidMethodNonVirtually), StaticPublicFlags);
internal static readonly MethodInfo ArgumentTransformationAttribute_Transform =
typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), InstancePublicFlags);
// ReSharper restore InconsistentNaming
}
internal static class ExpressionCache
{
internal static readonly Expression NullConstant = Expression.Constant(null);
internal static readonly Expression NullExecutionContext = Expression.Constant(null, typeof(ExecutionContext));
internal static readonly Expression NullPSObject = Expression.Constant(null, typeof(PSObject));
internal static readonly Expression NullEnumerator = Expression.Constant(null, typeof(IEnumerator));
internal static readonly Expression NullExtent = Expression.Constant(null, typeof(IScriptExtent));
internal static readonly Expression NullTypeTable = Expression.Constant(null, typeof(TypeTable));
internal static readonly Expression NullFormatProvider = Expression.Constant(null, typeof(IFormatProvider));
internal static readonly Expression NullObjectArray = Expression.Constant(null, typeof(object[]));
internal static readonly Expression AutomationNullConstant = Expression.Constant(AutomationNull.Value, typeof(object));
internal static readonly Expression NullCommandRedirections = Expression.Constant(null, typeof(CommandRedirection[][]));
internal static readonly Expression NullTypeArray = Expression.Constant(null, typeof(Type[]));
internal static readonly Expression NullType = Expression.Constant(null, typeof(Type));
internal static readonly Expression NullDelegateArray = Expression.Constant(null, typeof(Action<FunctionContext>[]));
internal static readonly Expression NullPipe = Expression.Constant(new Pipe { NullPipe = true });
internal static readonly Expression ConstEmptyString = Expression.Constant(string.Empty);
internal static readonly Expression CompareOptionsIgnoreCase = Expression.Constant(CompareOptions.IgnoreCase);
internal static readonly Expression CompareOptionsNone = Expression.Constant(CompareOptions.None);
internal static readonly Expression Ordinal = Expression.Constant(StringComparison.Ordinal);
internal static readonly Expression InvariantCulture = Expression.Constant(CultureInfo.InvariantCulture);
internal static readonly Expression OrdinalIgnoreCaseComparer = Expression.Constant(StringComparer.OrdinalIgnoreCase, typeof(StringComparer));
internal static readonly Expression CatchAllType = Expression.Constant(typeof(ExceptionHandlingOps.CatchAll), typeof(Type));
// Empty expression is used at the end of blocks to give them the void expression result
internal static readonly Expression Empty = Expression.Empty();
internal static readonly Expression GetExecutionContextFromTLS =
Expression.Call(CachedReflectionInfo.LocalPipeline_GetExecutionContextFromTLS);
internal static readonly Expression BoxedTrue = Expression.Field(null, typeof(Boxed).GetField("True", BindingFlags.Static | BindingFlags.NonPublic));
internal static readonly Expression BoxedFalse = Expression.Field(null, typeof(Boxed).GetField("False", BindingFlags.Static | BindingFlags.NonPublic));
private static readonly Expression[] s_intConstants = new Expression[102];
internal static Expression Constant(int i)
{
// We cache -1..100, anything else don't bother caching
if (i < -1 || i > 100)
{
return Expression.Constant(i);
}
Expression result = s_intConstants[i + 1];
if (result == null)
{
result = Expression.Constant(i);
s_intConstants[i + 1] = result;
}
return result;
}
internal static readonly Expression TrueConstant = Expression.Constant(true);
internal static readonly Expression FalseConstant = Expression.Constant(false);
internal static Expression Constant(bool b)
{
return b ? TrueConstant : FalseConstant;
}
}
internal static class ExpressionExtensions
{
internal static Expression Convert(this Expression expr, Type type)
{
if (expr.Type == type)
{
return expr;
}
if (expr.Type == typeof(void))
{
// If we're converting from void, use $null instead to figure the conversion.
expr = ExpressionCache.NullConstant;
}
var conversion = LanguagePrimitives.GetConversionRank(expr.Type, type);
if (conversion == ConversionRank.Assignable)
{
return Expression.Convert(expr, type);
}
if (type.ContainsGenericParameters || type.IsByRefLike)
{
return Expression.Call(
CachedReflectionInfo.LanguagePrimitives_ThrowInvalidCastException,
expr.Cast(typeof(object)),
Expression.Constant(type, typeof(Type)));
}
return DynamicExpression.Dynamic(PSConvertBinder.Get(type), type, expr);
}
internal static Expression Cast(this Expression expr, Type type)
{
if (expr.Type == type)
{
return expr;
}
if ((expr.Type.IsFloating() || expr.Type == typeof(decimal)) && type.IsPrimitive)
{
// Convert correctly handles most "primitive" conversions for PowerShell,
// but it does not correctly handle floating point.
expr = Expression.Call(
CachedReflectionInfo.Convert_ChangeType,
Expression.Convert(expr, typeof(object)),
Expression.Constant(type, typeof(Type)));
}
return Expression.Convert(expr, type);
}
#if ENABLE_BINDER_DEBUG_LOGGING
internal static string ToDebugString(this Expression expr)
{
using (var writer = new StringWriter(CultureInfo.InvariantCulture))
{
DebugViewWriter.WriteTo(expr, writer);
return writer.ToString();
}
}
#endif
}
internal class FunctionContext
{
internal ScriptBlock _scriptBlock;
internal string _file;
internal bool _debuggerHidden;
internal bool _debuggerStepThrough;
internal IScriptExtent[] _sequencePoints;
internal ExecutionContext _executionContext;
internal Pipe _outputPipe;
internal BitArray _breakPoints;
internal List<LineBreakpoint> _boundBreakpoints;
internal int _currentSequencePointIndex;
internal MutableTuple _localsTuple;
internal List<Tuple<Type[], Action<FunctionContext>[], Type[]>> _traps = new List<Tuple<Type[], Action<FunctionContext>[], Type[]>>();
internal string _functionName;
internal IScriptExtent CurrentPosition
{
get { return _sequencePoints != null ? _sequencePoints[_currentSequencePointIndex] : PositionUtilities.EmptyExtent; }
}
internal void PushTrapHandlers(Type[] type, Action<FunctionContext>[] handler, Type[] tupleType)
{
_traps.Add(Tuple.Create(type, handler, tupleType));
}
internal void PopTrapHandlers()
{
_traps.RemoveAt(_traps.Count - 1);
}
}
internal class Compiler : ICustomAstVisitor2
{
internal static readonly ParameterExpression s_executionContextParameter;
internal static readonly ParameterExpression s_functionContext;
private static readonly ParameterExpression s_returnPipe;
private static readonly Expression s_notDollarQuestion;
private static readonly Expression s_getDollarQuestion;
private static readonly Expression s_setDollarQuestionToTrue;
private static readonly Expression s_callCheckForInterrupts;
private static readonly Expression s_getCurrentPipe;
private static readonly Expression s_currentExceptionBeingHandled;
private static readonly CatchBlock s_catchFlowControl;
private static readonly CatchBlock[] s_stmtCatchHandlers;
internal static readonly Type DottedLocalsTupleType = MutableTuple.MakeTupleType(SpecialVariables.AutomaticVariableTypes);
internal static readonly Dictionary<string, int> DottedLocalsNameIndexMap =
new Dictionary<string, int>(SpecialVariables.AutomaticVariableTypes.Length, StringComparer.OrdinalIgnoreCase);
internal static readonly Dictionary<string, int> DottedScriptCmdletLocalsNameIndexMap =
new Dictionary<string, int>(
SpecialVariables.AutomaticVariableTypes.Length + SpecialVariables.PreferenceVariableTypes.Length,
StringComparer.OrdinalIgnoreCase);
static Compiler()
{
s_functionContext = Expression.Parameter(typeof(FunctionContext), "funcContext");
s_executionContextParameter = Expression.Variable(typeof(ExecutionContext), "context");
s_getDollarQuestion = Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue);
s_notDollarQuestion = Expression.Not(s_getDollarQuestion);
s_setDollarQuestionToTrue = Expression.Assign(
s_getDollarQuestion,
ExpressionCache.TrueConstant);
s_callCheckForInterrupts = Expression.Call(
CachedReflectionInfo.PipelineOps_CheckForInterrupts,
s_executionContextParameter);
s_getCurrentPipe = Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__outputPipe);
s_returnPipe = Expression.Variable(s_getCurrentPipe.Type, "returnPipe");
var exception = Expression.Variable(typeof(Exception), "exception");
s_catchFlowControl = Expression.Catch(typeof(FlowControlException), Expression.Rethrow());
var catchAll = Expression.Catch(
exception,
Expression.Block(
Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference,
Compiler.s_functionContext, exception)));
s_stmtCatchHandlers = new CatchBlock[] { s_catchFlowControl, catchAll };
s_currentExceptionBeingHandled = Expression.Property(
s_executionContextParameter, CachedReflectionInfo.ExecutionContext_CurrentExceptionBeingHandled);
int i;
for (i = 0; i < SpecialVariables.AutomaticVariables.Length; ++i)
{
DottedLocalsNameIndexMap.Add(SpecialVariables.AutomaticVariables[i], i);
DottedScriptCmdletLocalsNameIndexMap.Add(SpecialVariables.AutomaticVariables[i], i);
}
for (i = 0; i < SpecialVariables.PreferenceVariables.Length; ++i)
{
DottedScriptCmdletLocalsNameIndexMap.Add(
SpecialVariables.PreferenceVariables[i],
i + (int)AutomaticVariable.NumberOfAutomaticVariables);
}
s_builtinAttributeGenerator.Add(typeof(CmdletBindingAttribute), NewCmdletBindingAttribute);
s_builtinAttributeGenerator.Add(typeof(ExperimentalAttribute), NewExperimentalAttribute);
s_builtinAttributeGenerator.Add(typeof(ParameterAttribute), NewParameterAttribute);
s_builtinAttributeGenerator.Add(typeof(OutputTypeAttribute), NewOutputTypeAttribute);
s_builtinAttributeGenerator.Add(typeof(AliasAttribute), NewAliasAttribute);
s_builtinAttributeGenerator.Add(typeof(ValidateSetAttribute), NewValidateSetAttribute);
s_builtinAttributeGenerator.Add(typeof(DebuggerHiddenAttribute), NewDebuggerHiddenAttribute);
s_builtinAttributeGenerator.Add(typeof(ValidateNotNullAttribute), NewValidateNotNullAttribute);
s_builtinAttributeGenerator.Add(typeof(ValidateNotNullOrEmptyAttribute), NewValidateNotNullOrEmptyAttribute);
}
private Compiler(List<IScriptExtent> sequencePoints, Dictionary<IScriptExtent, int> sequencePointIndexMap)
{
_sequencePoints = sequencePoints;
_sequencePointIndexMap = sequencePointIndexMap;
}
internal Compiler()
{
_sequencePoints = new List<IScriptExtent>();
_sequencePointIndexMap = new Dictionary<IScriptExtent, int>();
}
internal bool CompilingConstantExpression { get; set; }
internal bool Optimize { get; private set; }
internal Type LocalVariablesTupleType { get; private set; }
internal ParameterExpression LocalVariablesParameter { get; private set; }
private SymbolDocumentInfo _debugSymbolDocument;
internal TypeDefinitionAst _memberFunctionType;
private bool _compilingTrap;
private bool _compilingSingleExpression;
private bool _compilingScriptCmdlet;
private string _currentFunctionName;
private int _switchTupleIndex = VariableAnalysis.Unanalyzed;
private int _foreachTupleIndex = VariableAnalysis.Unanalyzed;
private readonly List<IScriptExtent> _sequencePoints;
private readonly Dictionary<IScriptExtent, int> _sequencePointIndexMap;
private int _stmtCount;
internal bool CompilingMemberFunction { get; set; }
internal SpecialMemberFunctionType SpecialMemberFunctionType { get; set; }
internal Type MemberFunctionReturnType
{
get
{
Diagnostics.Assert(CompilingMemberFunction, "Return not only set for member functions");
return _memberFunctionReturnType;
}
set
{
_memberFunctionReturnType = value;
}
}
private Type _memberFunctionReturnType;
#region Helpers for AST Compile methods
internal Expression Compile(Ast ast)
{
return (Expression)ast.Accept(this);
}
internal Expression CompileExpressionOperand(ExpressionAst exprAst)
{
var result = Compile(exprAst);
if (result.Type == typeof(void))
{
result = Expression.Block(result, ExpressionCache.NullConstant);
}
return result;
}
private IEnumerable<Expression> CompileInvocationArguments(IReadOnlyList<ExpressionAst> arguments)
{
if (arguments is null || arguments.Count == 0)
{
return Array.Empty<Expression>();
}
var result = new Expression[arguments.Count];
for (int i = 0; i < result.Length; i++)
{
result[i] = CompileExpressionOperand(arguments[i]);
}
return result;
}
internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKind, Expression right)
{
IAssignableValue av = left.GetAssignableValue();
ExpressionType et = ExpressionType.Extension;
switch (tokenKind)
{
case TokenKind.Equals: return av.SetValue(this, right);
case TokenKind.PlusEquals: et = ExpressionType.Add; break;
case TokenKind.MinusEquals: et = ExpressionType.Subtract; break;
case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break;
case TokenKind.DivideEquals: et = ExpressionType.Divide; break;
case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break;
case TokenKind.QuestionQuestionEquals: et = ExpressionType.Coalesce; break;
}
var exprs = new List<Expression>();
var temps = new List<ParameterExpression>();
var getExpr = av.GetValue(this, exprs, temps);
if (et == ExpressionType.Coalesce)
{
exprs.Add(av.SetValue(this, Coalesce(getExpr, right)));
}
else
{
exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right)));
}
return Expression.Block(temps, exprs);
}
private static Expression Coalesce(Expression left, Expression right)
{
Type leftType = left.Type;
if (leftType.IsValueType)
{
return left;
}
else
{
ParameterExpression lhsStoreVar = Expression.Variable(typeof(object));
var blockParameters = new ParameterExpression[] { lhsStoreVar };
var blockStatements = new Expression[]
{
Expression.Assign(lhsStoreVar, left.Cast(typeof(object))),
Expression.Condition(
Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNull, lhsStoreVar),
right.Cast(typeof(object)),
lhsStoreVar),
};
return Expression.Block(
typeof(object),
blockParameters,
blockStatements);
}
}
internal Expression GetLocal(int tupleIndex)
{
Expression result = LocalVariablesParameter;
foreach (var property in MutableTuple.GetAccessPath(LocalVariablesTupleType, tupleIndex))
{
result = Expression.Property(result, property);
}
return result;
}
internal static Expression CallGetVariable(Expression variablePath, VariableExpressionAst varAst)
{
return Expression.Call(
CachedReflectionInfo.VariableOps_GetVariableValue,
variablePath,
s_executionContextParameter,
Expression.Constant(varAst).Cast(typeof(VariableExpressionAst)));
}
internal static Expression CallSetVariable(Expression variablePath, Expression rhs, Expression attributes = null)
{
return Expression.Call(
CachedReflectionInfo.VariableOps_SetVariableValue,
variablePath, rhs.Cast(typeof(object)),
s_executionContextParameter,
attributes ?? ExpressionCache.NullConstant.Cast(typeof(AttributeAst[])));
}
internal Expression GetAutomaticVariable(VariableExpressionAst varAst)
{
// Generate, in psuedo code:
//
// return (localsTuple.IsValueSet(tupleIndex)
// ? localsTuple.ItemXXX
// : VariableOps.GetAutomaticVariable(tupleIndex, executionContextParameter);
//
// We could be smarter about the generated code. For example:
//
// * $PSCmdlet - always set if the script uses cmdletbinding.
// * $_ - always set in process and end block, otherwise need dynamic checks.
// * $this - can never know if it's set, always need above psuedo code.
// * $input - also can never know - it's always set from a command process, but not necessarily set from ScriptBlock.Invoke.
//
// These optimizations are not yet performed.
//
int tupleIndex = varAst.TupleIndex;
var expr = GetLocal(tupleIndex);
var callGetAutomaticVariable = Expression.Call(
CachedReflectionInfo.VariableOps_GetAutomaticVariableValue,
ExpressionCache.Constant(tupleIndex),
s_executionContextParameter,
Expression.Constant(varAst)).Convert(expr.Type);
if (!Optimize)
return callGetAutomaticVariable;
return Expression.Condition(
Expression.Call(
LocalVariablesParameter,
CachedReflectionInfo.MutableTuple_IsValueSet,
ExpressionCache.Constant(tupleIndex)),
expr,
callGetAutomaticVariable);
}
internal static Expression CallStringEquals(Expression left, Expression right, bool ignoreCase)
{
return Expression.Call(
CachedReflectionInfo.StringOps_Equals,
left,
right,
ExpressionCache.InvariantCulture,
ignoreCase ? ExpressionCache.CompareOptionsIgnoreCase : ExpressionCache.CompareOptionsNone);
}
internal static Expression IsStrictMode(int version, Expression executionContext = null)
{
if (executionContext == null)
{
executionContext = ExpressionCache.NullExecutionContext;
}
return Expression.Call(
CachedReflectionInfo.ExecutionContext_IsStrictVersion,
executionContext,
ExpressionCache.Constant(version));
}
private int AddSequencePoint(IScriptExtent extent)
{
// Make sure we don't add the same extent to the sequence point list twice.
if (!_sequencePointIndexMap.TryGetValue(extent, out int index))
{
_sequencePoints.Add(extent);
index = _sequencePoints.Count - 1;
_sequencePointIndexMap.Add(extent, index);
}
return index;
}
private Expression UpdatePosition(Ast ast)
{
IScriptExtent extent = ast.Extent;
int index = AddSequencePoint(extent);
// If we just added the first sequence point, then we don't want to check for breakpoints - we'll do that
// in EnterScriptFunction.
// Except for while/do loops, in this case we want to check breakpoints on the first sequence point since it
// will be executed multiple times.
return (index == 0 && !_generatingWhileOrDoLoop)
? ExpressionCache.Empty
: new UpdatePositionExpr(extent, index, _debugSymbolDocument, !_compilingSingleExpression);
}
private int _tempCounter;
internal ParameterExpression NewTemp(Type type, string name)
{
return Expression.Variable(type, string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, _tempCounter++));
}
internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr)
{
while (expr is ParenExpressionAst)
{
expr = ((ParenExpressionAst)expr).Pipeline.GetPureExpression();
}
ConvertExpressionAst firstConvert = null;
while (expr is AttributedExpressionAst)
{
if (expr is ConvertExpressionAst && !((ConvertExpressionAst)expr).IsRef())
{
firstConvert = (ConvertExpressionAst)expr;
break;
}
expr = ((AttributedExpressionAst)expr).Child;
}
return firstConvert?.Type.TypeName.GetReflectionType();
}
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType)
{
if (targetType == null && argType == null)
{
return null;
}
return new PSMethodInvocationConstraints(targetType, new[] { argType });
}
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type[] argTypes)
{
if (targetType == null && (argTypes == null || argTypes.Length == 0))
{
return null;
}
return new PSMethodInvocationConstraints(targetType, argTypes);
}
internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr)
{
var typeName = typeConstraint.TypeName;
var toType = typeName.GetReflectionType();
if (toType != null)
{
if (toType == typeof(void))
{
return Expression.Block(typeof(void), expr);
}
return expr.Convert(toType);
}
// typeName can't be resolved at compile time, so defer resolution until runtime.
return DynamicExpression.Dynamic(
PSDynamicConvertBinder.Get(),
typeof(object),
Expression.Call(
CachedReflectionInfo.TypeOps_ResolveTypeName,
Expression.Constant(typeName),
Expression.Constant(typeName.Extent)),
expr);
}
internal static Expression ConvertValue(Expression expr, List<AttributeBaseAst> conversions)
{
for (int index = 0; index < conversions.Count; index++)
{
var typeConstraint = conversions[index] as TypeConstraintAst;
if (typeConstraint != null)
{
expr = ConvertValue(typeConstraint, expr);
}
}
return expr;
}
#endregion Helpers for AST Compile methods
#region Parameter Metadata
internal static RuntimeDefinedParameterDictionary GetParameterMetaData(ReadOnlyCollection<ParameterAst> parameters, bool automaticPositions, ref bool usesCmdletBinding)
{
var runtimeDefinedParamDict = new RuntimeDefinedParameterDictionary();
var runtimeDefinedParamList = new List<RuntimeDefinedParameter>(parameters.Count);
var customParameterSet = false;
for (int index = 0; index < parameters.Count; index++)
{
var param = parameters[index];
var rdp = GetRuntimeDefinedParameter(param, ref customParameterSet, ref usesCmdletBinding);
if (rdp != null)
{
runtimeDefinedParamList.Add(rdp);
runtimeDefinedParamDict.Add(param.Name.VariablePath.UserPath, rdp);
}
}
int pos = 0;
if (automaticPositions && !customParameterSet)
{
for (int index = 0; index < runtimeDefinedParamList.Count; index++)
{
var rdp = runtimeDefinedParamList[index];
var paramAttribute = (ParameterAttribute)rdp.Attributes.First(static attr => attr is ParameterAttribute);
if (rdp.ParameterType != typeof(SwitchParameter))
{
paramAttribute.Position = pos++;
}
}
}
runtimeDefinedParamDict.Data = runtimeDefinedParamList.ToArray();
return runtimeDefinedParamDict;
}
private static readonly Dictionary<CallInfo, Delegate> s_attributeGeneratorCache = new Dictionary<CallInfo, Delegate>();
private static readonly Dictionary<Type, Func<AttributeAst, Attribute>> s_builtinAttributeGenerator = new Dictionary<Type, Func<AttributeAst, Attribute>>(10);
private static Delegate GetAttributeGenerator(CallInfo callInfo)
{
Delegate result;
lock (s_attributeGeneratorCache)
{
if (!s_attributeGeneratorCache.TryGetValue(callInfo, out result))
{
var binder = PSAttributeGenerator.Get(callInfo);
var parameters = new ParameterExpression[callInfo.ArgumentCount + 1];
for (int i = 0; i < parameters.Length; ++i)
{
parameters[i] = Expression.Variable(typeof(object));
}
result = Expression.Lambda(DynamicExpression.Dynamic(binder, typeof(object), parameters), parameters).Compile();
s_attributeGeneratorCache.Add(callInfo, result);
}
}
return result;
}
private static readonly CallSite<Func<CallSite, object, int>> s_attrArgToIntConverter =
CallSite<Func<CallSite, object, int>>.Create(PSConvertBinder.Get(typeof(int)));
internal static readonly CallSite<Func<CallSite, object, string>> s_attrArgToStringConverter =
CallSite<Func<CallSite, object, string>>.Create(PSConvertBinder.Get(typeof(string)));
private static readonly CallSite<Func<CallSite, object, string[]>> s_attrArgToStringArrayConverter =
CallSite<Func<CallSite, object, string[]>>.Create(PSConvertBinder.Get(typeof(string[])));
private static readonly CallSite<Func<CallSite, object, bool>> s_attrArgToBoolConverter =
CallSite<Func<CallSite, object, bool>>.Create(PSConvertBinder.Get(typeof(bool)));
private static readonly CallSite<Func<CallSite, object, ConfirmImpact>> s_attrArgToConfirmImpactConverter =
CallSite<Func<CallSite, object, ConfirmImpact>>.Create(PSConvertBinder.Get(typeof(ConfirmImpact)));
private static readonly CallSite<Func<CallSite, object, RemotingCapability>> s_attrArgToRemotingCapabilityConverter =
CallSite<Func<CallSite, object, RemotingCapability>>.Create(PSConvertBinder.Get(typeof(RemotingCapability)));
private static readonly CallSite<Func<CallSite, object, ExperimentAction>> s_attrArgToExperimentActionConverter =
CallSite<Func<CallSite, object, ExperimentAction>>.Create(PSConvertBinder.Get(typeof(ExperimentAction)));
private static readonly ConstantValueVisitor s_cvv = new ConstantValueVisitor { AttributeArgument = true };
private static void CheckNoPositionalArgs(AttributeAst ast)
{
var positionalArgCount = ast.PositionalArguments.Count;
if (positionalArgCount > 0)
{
throw InterpreterError.NewInterpreterException(
null,
typeof(MethodException),
ast.Extent,
"MethodCountCouldNotFindBest",
ExtendedTypeSystem.MethodArgumentCountException,
".ctor",
positionalArgCount);
}
}
private static void CheckNoNamedArgs(AttributeAst ast)
{
if (ast.NamedArguments.Count > 0)
{
var namedArg = ast.NamedArguments[0];
var argumentName = namedArg.ArgumentName;
throw InterpreterError.NewInterpreterException(
namedArg,
typeof(RuntimeException),
namedArg.Extent,
"PropertyNotFoundForType",
ParserStrings.PropertyNotFoundForType,
argumentName,
typeof(CmdletBindingAttribute));
}
}
private static (string, ExperimentAction) GetFeatureNameAndAction(AttributeAst ast)
{
var argValue0 = ast.PositionalArguments[0].Accept(s_cvv);
var argValue1 = ast.PositionalArguments[1].Accept(s_cvv);
var featureName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue0);
var action = s_attrArgToExperimentActionConverter.Target(s_attrArgToExperimentActionConverter, argValue1);
return (featureName, action);
}
private static Attribute NewCmdletBindingAttribute(AttributeAst ast)
{
CheckNoPositionalArgs(ast);
var result = new CmdletBindingAttribute();
foreach (var namedArg in ast.NamedArguments)
{
var argValue = namedArg.Argument.Accept(s_cvv);
var argumentName = namedArg.ArgumentName;
if (argumentName.Equals("DefaultParameterSetName", StringComparison.OrdinalIgnoreCase))
{
result.DefaultParameterSetName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("HelpUri", StringComparison.OrdinalIgnoreCase))
{
result.HelpUri = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("SupportsShouldProcess", StringComparison.OrdinalIgnoreCase))
{
result.SupportsShouldProcess = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("PositionalBinding", StringComparison.OrdinalIgnoreCase))
{
result.PositionalBinding = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("ConfirmImpact", StringComparison.OrdinalIgnoreCase))
{
result.ConfirmImpact = s_attrArgToConfirmImpactConverter.Target(s_attrArgToConfirmImpactConverter, argValue);
}
else if (argumentName.Equals("SupportsTransactions", StringComparison.OrdinalIgnoreCase))
{
result.SupportsTransactions = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("SupportsPaging", StringComparison.OrdinalIgnoreCase))
{
result.SupportsPaging = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("RemotingCapability", StringComparison.OrdinalIgnoreCase))
{
result.RemotingCapability = s_attrArgToRemotingCapabilityConverter.Target(s_attrArgToRemotingCapabilityConverter, argValue);
}
else
{
throw InterpreterError.NewInterpreterException(
namedArg,
typeof(RuntimeException),
namedArg.Extent,
"PropertyNotFoundForType",
ParserStrings.PropertyNotFoundForType,
argumentName,
typeof(CmdletBindingAttribute));
}
}
return result;
}
private static Attribute NewExperimentalAttribute(AttributeAst ast)
{
int positionalArgCount = ast.PositionalArguments.Count;
if (positionalArgCount != 2)
{
throw InterpreterError.NewInterpreterException(
targetObject: null,
typeof(MethodException),
ast.Extent,
"MethodCountCouldNotFindBest",
ExtendedTypeSystem.MethodArgumentCountException,
".ctor",
positionalArgCount);
}
(string name, ExperimentAction action) = GetFeatureNameAndAction(ast);
return new ExperimentalAttribute(name, action);
}
private static Attribute NewParameterAttribute(AttributeAst ast)
{
ParameterAttribute result;
int positionalArgCount = ast.PositionalArguments.Count;
switch (positionalArgCount)
{
case 0:
result = new ParameterAttribute();
break;
case 2:
(string name, ExperimentAction action) = GetFeatureNameAndAction(ast);
result = new ParameterAttribute(name, action);
break;
default:
throw InterpreterError.NewInterpreterException(
targetObject: null,
typeof(MethodException),
ast.Extent,
"MethodCountCouldNotFindBest",
ExtendedTypeSystem.MethodArgumentCountException,
".ctor",
positionalArgCount);
}
foreach (var namedArg in ast.NamedArguments)
{
var argValue = namedArg.Argument.Accept(s_cvv);
var argumentName = namedArg.ArgumentName;
if (argumentName.Equals("Position", StringComparison.OrdinalIgnoreCase))
{
result.Position = s_attrArgToIntConverter.Target(s_attrArgToIntConverter, argValue);
}
else if (argumentName.Equals("ParameterSetName", StringComparison.OrdinalIgnoreCase))
{
result.ParameterSetName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("Mandatory", StringComparison.OrdinalIgnoreCase))
{
result.Mandatory = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("ValueFromPipeline", StringComparison.OrdinalIgnoreCase))
{
result.ValueFromPipeline = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("ValueFromPipelineByPropertyName", StringComparison.OrdinalIgnoreCase))
{
result.ValueFromPipelineByPropertyName = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("ValueFromRemainingArguments", StringComparison.OrdinalIgnoreCase))
{
result.ValueFromRemainingArguments = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("HelpMessage", StringComparison.OrdinalIgnoreCase))
{
result.HelpMessage = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("HelpMessageBaseName", StringComparison.OrdinalIgnoreCase))
{
result.HelpMessageBaseName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("HelpMessageResourceId", StringComparison.OrdinalIgnoreCase))
{
result.HelpMessageResourceId = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else if (argumentName.Equals("DontShow", StringComparison.OrdinalIgnoreCase))
{
result.DontShow = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else
{
throw InterpreterError.NewInterpreterException(
namedArg,
typeof(RuntimeException),
namedArg.Extent,
"PropertyNotFoundForType",
ParserStrings.PropertyNotFoundForType,
argumentName,
typeof(CmdletBindingAttribute));
}
}
return result;
}
private static Attribute NewOutputTypeAttribute(AttributeAst ast)
{
OutputTypeAttribute result;
if (ast.PositionalArguments.Count == 0)
{
result = new OutputTypeAttribute(Array.Empty<string>());
}
else if (ast.PositionalArguments.Count == 1)
{
var typeArg = ast.PositionalArguments[0] as TypeExpressionAst;
if (typeArg != null)
{
var type = TypeOps.ResolveTypeName(typeArg.TypeName, typeArg.Extent);
result = new OutputTypeAttribute(type);
}
else
{
var argValue = ast.PositionalArguments[0].Accept(s_cvv);
result = new OutputTypeAttribute(s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue));
}
}
else
{
var args = new object[ast.PositionalArguments.Count];
for (int i = 0; i < ast.PositionalArguments.Count; i++)
{
var positionalArgument = ast.PositionalArguments[i];
var typeArg = positionalArgument as TypeExpressionAst;
args[i] = typeArg != null
? TypeOps.ResolveTypeName(typeArg.TypeName, typeArg.Extent)
: positionalArgument.Accept(s_cvv);
}
if (args[0] is Type)
{
result = new OutputTypeAttribute(LanguagePrimitives.ConvertTo<Type[]>(args));
}
else
{
result = new OutputTypeAttribute(LanguagePrimitives.ConvertTo<string[]>(args));
}
}
foreach (var namedArg in ast.NamedArguments)
{
var argValue = namedArg.Argument.Accept(s_cvv);
var argumentName = namedArg.ArgumentName;
if (argumentName.Equals("ParameterSetName", StringComparison.OrdinalIgnoreCase))
{
result.ParameterSetName = s_attrArgToStringArrayConverter.Target(s_attrArgToStringArrayConverter, argValue);
}
else if (argumentName.Equals("ProviderCmdlet", StringComparison.OrdinalIgnoreCase))
{
result.ProviderCmdlet = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue);
}
else
{
throw InterpreterError.NewInterpreterException(
namedArg,
typeof(RuntimeException),
namedArg.Extent,
"PropertyNotFoundForType",
ParserStrings.PropertyNotFoundForType,
argumentName,
typeof(CmdletBindingAttribute));
}
}
return result;
}
private static Attribute NewAliasAttribute(AttributeAst ast)
{
CheckNoNamedArgs(ast);
var args = new string[ast.PositionalArguments.Count];
for (int i = 0; i < ast.PositionalArguments.Count; i++)
{
args[i] = s_attrArgToStringConverter.Target(
s_attrArgToStringConverter,
ast.PositionalArguments[i].Accept(s_cvv));
}
return new AliasAttribute(args);
}
private static Attribute NewValidateSetAttribute(AttributeAst ast)
{
ValidateSetAttribute result;
// 'ValidateSet([CustomGeneratorType], IgnoreCase=$false)' is supported in scripts.
if (ast.PositionalArguments.Count == 1 && ast.PositionalArguments[0] is TypeExpressionAst generatorTypeAst)
{
var generatorType = TypeResolver.ResolveITypeName(generatorTypeAst.TypeName, out Exception exception);
if (generatorType != null)
{
result = new ValidateSetAttribute(generatorType);
}
else
{
throw InterpreterError.NewInterpreterExceptionWithInnerException(
ast,
typeof(RuntimeException),
ast.Extent,
"TypeNotFound",
ParserStrings.TypeNotFound,
exception,
generatorTypeAst.TypeName.FullName,
typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName);
}
}
else
{
// 'ValidateSet("value1","value2", IgnoreCase=$false)' is supported in scripts.
var args = new string[ast.PositionalArguments.Count];
for (int i = 0; i < ast.PositionalArguments.Count; i++)
{
args[i] = s_attrArgToStringConverter.Target(
s_attrArgToStringConverter,
ast.PositionalArguments[i].Accept(s_cvv));
}
result = new ValidateSetAttribute(args);
}
foreach (var namedArg in ast.NamedArguments)
{
var argValue = namedArg.Argument.Accept(s_cvv);
var argumentName = namedArg.ArgumentName;
if (argumentName.Equals("IgnoreCase", StringComparison.OrdinalIgnoreCase))
{
result.IgnoreCase = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue);
}
else if (argumentName.Equals("ErrorMessage", StringComparison.OrdinalIgnoreCase))
{
result.ErrorMessage = argValue.ToString();
}
else
{
throw InterpreterError.NewInterpreterException(
namedArg,
typeof(RuntimeException),
namedArg.Extent,
"PropertyNotFoundForType",
ParserStrings.PropertyNotFoundForType,
argumentName,
typeof(CmdletBindingAttribute));
}
}
return result;
}
private static Attribute NewDebuggerHiddenAttribute(AttributeAst ast)
{
CheckNoPositionalArgs(ast);
CheckNoNamedArgs(ast);
return new DebuggerHiddenAttribute();
}
private static Attribute NewValidateNotNullOrEmptyAttribute(AttributeAst ast)
{
CheckNoPositionalArgs(ast);
CheckNoNamedArgs(ast);
return new ValidateNotNullOrEmptyAttribute();
}
private static Attribute NewValidateNotNullAttribute(AttributeAst ast)
{
CheckNoPositionalArgs(ast);
CheckNoNamedArgs(ast);
return new ValidateNotNullAttribute();
}
internal static Attribute GetAttribute(AttributeAst attributeAst)
{
var attributeType = attributeAst.TypeName.GetReflectionAttributeType();
if (attributeType == null)
{
throw InterpreterError.NewInterpreterException(
attributeAst,
typeof(RuntimeException),
attributeAst.Extent,
"CustomAttributeTypeNotFound",
ParserStrings.CustomAttributeTypeNotFound,
attributeAst.TypeName.FullName);
}
Func<AttributeAst, Attribute> generator;
if (s_builtinAttributeGenerator.TryGetValue(attributeType, out generator))
{
return generator(attributeAst);
}
var positionalArgCount = attributeAst.PositionalArguments.Count;
var argumentNames = attributeAst.NamedArguments.Select(static name => name.ArgumentName).ToArray();
var totalArgCount = positionalArgCount + argumentNames.Length;
var callInfo = new CallInfo(totalArgCount, argumentNames);
var delegateArgs = new object[totalArgCount + 1];
delegateArgs[0] = attributeType;
int i = 1;
for (int index = 0; index < attributeAst.PositionalArguments.Count; index++)
{
var posArg = attributeAst.PositionalArguments[index];
delegateArgs[i++] = posArg.Accept(s_cvv);
}
for (int index = 0; index < attributeAst.NamedArguments.Count; index++)
{
var namedArg = attributeAst.NamedArguments[index];
delegateArgs[i++] = namedArg.Argument.Accept(s_cvv);
}
try
{
return (Attribute)GetAttributeGenerator(callInfo).DynamicInvoke(delegateArgs);
}
catch (TargetInvocationException tie)
{
// Unwrap the wrapped exception
var innerException = tie.InnerException;
var rte = innerException as RuntimeException;
if (rte == null)
{
rte = InterpreterError.NewInterpreterExceptionWithInnerException(
null,
typeof(RuntimeException),
attributeAst.Extent,
"ExceptionConstructingAttribute",
ExtendedTypeSystem.ExceptionConstructingAttribute,
innerException,
innerException.Message,
attributeAst.TypeName.FullName);
}
InterpreterError.UpdateExceptionErrorRecordPosition(rte, attributeAst.Extent);
throw rte;
}
}
internal static Attribute GetAttribute(TypeConstraintAst typeConstraintAst)
{
Type type = null;
var ihct = typeConstraintAst.TypeName as ISupportsTypeCaching;
if (ihct != null)
{
type = ihct.CachedType;
}
if (type == null)
{
type = TypeOps.ResolveTypeName(typeConstraintAst.TypeName, typeConstraintAst.Extent);
if (ihct != null)
{
ihct.CachedType = type;
}
}
return new ArgumentTypeConverterAttribute(type);
}
private static RuntimeDefinedParameter GetRuntimeDefinedParameter(ParameterAst parameterAst, ref bool customParameterSet, ref bool usesCmdletBinding)
{
var attributes = new List<Attribute>(parameterAst.Attributes.Count);
bool hasParameterAttribute = false;
bool hasEnabledParamAttribute = false;
bool hasSeenExpAttribute = false;
for (int index = 0; index < parameterAst.Attributes.Count; index++)
{
var attributeAst = parameterAst.Attributes[index];
var attribute = attributeAst.GetAttribute();
if (attribute is ExperimentalAttribute expAttribute)
{
// Only honor the first seen experimental attribute, ignore the others.
if (!hasSeenExpAttribute && expAttribute.ToHide)
{
return null;
}
// Do not add experimental attributes to the attribute list.
hasSeenExpAttribute = true;
continue;
}
else if (attribute is ParameterAttribute paramAttribute)
{
hasParameterAttribute = true;
if (paramAttribute.ToHide)
{
continue;
}
hasEnabledParamAttribute = true;
usesCmdletBinding = true;
if (paramAttribute.Position != int.MinValue ||
!paramAttribute.ParameterSetName.Equals(
ParameterAttribute.AllParameterSets,
StringComparison.OrdinalIgnoreCase))
{
customParameterSet = true;
}
}
attributes.Add(attribute);
}
// If all 'ParameterAttribute' declared for the parameter are hidden due to
// an experimental feature, then the parameter should be ignored.
if (hasParameterAttribute && !hasEnabledParamAttribute)
{
return null;
}
attributes.Reverse();
if (!hasParameterAttribute)
{
attributes.Insert(0, new ParameterAttribute());
}
var result = new RuntimeDefinedParameter(parameterAst.Name.VariablePath.UserPath, parameterAst.StaticType,
new Collection<Attribute>(attributes));
if (parameterAst.DefaultValue != null)
{
object constantValue;
if (IsConstantValueVisitor.IsConstant(parameterAst.DefaultValue, out constantValue))
{
result.Value = constantValue;
}
else
{
// Expression isn't constant, create a wrapper that holds the ast, and if necessary,
// will cache a delegate to evaluate the default value.
result.Value = new DefaultValueExpressionWrapper { Expression = parameterAst.DefaultValue };
}
}
else
{
object defaultValue;
if (TryGetDefaultParameterValue(parameterAst.StaticType, out defaultValue) && defaultValue != null)
{
// Skip setting the value when defaultValue is null because if we do call the setter,
// we'll try converting null to the parameter, which we might not want, e.g. if the parameter is [ref].
result.Value = defaultValue;
}
}
return result;
}
internal static bool TryGetDefaultParameterValue(Type type, out object value)
{
if (type == typeof(string))
{
value = string.Empty;
return true;
}
if (type.IsClass)
{
value = null;
return true;
}
if (type == typeof(bool))
{
value = Boxed.False;
return true;
}
if (type == typeof(SwitchParameter))
{
value = new SwitchParameter(false);
return true;
}
if (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)) && !type.IsEnum)
{
value = 0;
return true;
}
value = null;
return false;
}
internal class DefaultValueExpressionWrapper
{
internal ExpressionAst Expression { get; set; }
private Func<FunctionContext, object> _delegate;
private IScriptExtent[] _sequencePoints;
private Type LocalsTupleType;
internal object GetValue(ExecutionContext context, SessionStateInternal sessionStateInternal, IDictionary usingValues = null)
{
lock (this)
{
// Code written as part of a default value in a parameter is considered trusted
return Compiler.GetExpressionValue(
this.Expression,
true,
context,
sessionStateInternal,
usingValues,
ref _delegate,
ref _sequencePoints,
ref LocalsTupleType);
}
}
}
#endregion Parameter Metadata
// This is the main entry point for turning an AST into compiled code.
internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize)
{
var body = scriptBlock.Ast;
Diagnostics.Assert(
body is ScriptBlockAst || body is FunctionDefinitionAst || body is FunctionMemberAst || body is CompilerGeneratedMemberFunctionAst,
"Caller to verify ast is correct type.");
var ast = (Ast)body;
Optimize = optimize;
_compilingScriptCmdlet = scriptBlock.UsesCmdletBinding;
var fileName = ast.Extent.File;
if (fileName != null)
{
_debugSymbolDocument = Expression.SymbolDocument(fileName);
}
var details = VariableAnalysis.Analyze(body, !optimize, _compilingScriptCmdlet);
LocalVariablesTupleType = details.Item1;
var nameToIndexMap = details.Item2;
if (!nameToIndexMap.TryGetValue(SpecialVariables.@switch, out _switchTupleIndex))
{
_switchTupleIndex = VariableAnalysis.ForceDynamic;
}
if (!nameToIndexMap.TryGetValue(SpecialVariables.@foreach, out _foreachTupleIndex))
{
_foreachTupleIndex = VariableAnalysis.ForceDynamic;
}
LocalVariablesParameter = Expression.Variable(LocalVariablesTupleType, "locals");
var functionMemberAst = ast as FunctionMemberAst;
if (functionMemberAst != null)
{
CompilingMemberFunction = true;
MemberFunctionReturnType = functionMemberAst.GetReturnType();
_memberFunctionType = (TypeDefinitionAst)functionMemberAst.Parent;
SpecialMemberFunctionType = SpecialMemberFunctionType.None;
if (functionMemberAst.Name.Equals(_memberFunctionType.Name, StringComparison.OrdinalIgnoreCase))
{
// TODO: default argument support
var parameters = ((IParameterMetadataProvider)functionMemberAst.Body).Parameters;
if (parameters == null || parameters.Count == 0)
{
SpecialMemberFunctionType = functionMemberAst.IsStatic
? SpecialMemberFunctionType.StaticConstructor
: SpecialMemberFunctionType.DefaultConstructor;
}
}
}
else
{
var generatedMemberFunctionAst = ast as CompilerGeneratedMemberFunctionAst;
if (generatedMemberFunctionAst != null)
{
CompilingMemberFunction = true;
SpecialMemberFunctionType = generatedMemberFunctionAst.Type;
MemberFunctionReturnType = typeof(void);
_memberFunctionType = generatedMemberFunctionAst.DefiningType;
}
}
body.Body.Accept(this);
if (_sequencePoints.Count == 0)
{
// Uncommon, but possible if a script is empty, or if it only defines functions.
// In this case, add the entire body as a sequence point. Debugging won't stop
// on this sequence point, but it makes it safe to access the CurrentPosition
// property in FunctionContext (which can happen if there are exceptions
// defining the functions.)
AddSequencePoint(ast.Extent);
}
var compileInterpretChoice = (_stmtCount > 300) ? CompileInterpretChoice.NeverCompile : CompileInterpretChoice.CompileOnDemand;
if (optimize)
{
scriptBlock.DynamicParamBlock = CompileTree(_dynamicParamBlockLambda, compileInterpretChoice);
scriptBlock.BeginBlock = CompileTree(_beginBlockLambda, compileInterpretChoice);
scriptBlock.ProcessBlock = CompileTree(_processBlockLambda, compileInterpretChoice);
scriptBlock.EndBlock = CompileTree(_endBlockLambda, compileInterpretChoice);
scriptBlock.LocalsMutableTupleType = LocalVariablesTupleType;
scriptBlock.LocalsMutableTupleCreator = MutableTuple.TupleCreator(LocalVariablesTupleType);
scriptBlock.NameToIndexMap = nameToIndexMap;
}
else
{
scriptBlock.UnoptimizedDynamicParamBlock = CompileTree(_dynamicParamBlockLambda, compileInterpretChoice);
scriptBlock.UnoptimizedBeginBlock = CompileTree(_beginBlockLambda, compileInterpretChoice);
scriptBlock.UnoptimizedProcessBlock = CompileTree(_processBlockLambda, compileInterpretChoice);
scriptBlock.UnoptimizedEndBlock = CompileTree(_endBlockLambda, compileInterpretChoice);
scriptBlock.UnoptimizedLocalsMutableTupleType = LocalVariablesTupleType;
scriptBlock.UnoptimizedLocalsMutableTupleCreator = MutableTuple.TupleCreator(LocalVariablesTupleType);
}
// The sequence points are identical optimized or not. Regardless, we want to ensure
// that the list is unique no matter when the property is accessed, so make sure it is set just once.
if (scriptBlock.SequencePoints == null)
{
scriptBlock.SequencePoints = _sequencePoints.ToArray();
}
}
private static Action<FunctionContext> CompileTree(Expression<Action<FunctionContext>> lambda, CompileInterpretChoice compileInterpretChoice)
{
if (lambda == null)
{
return null;
}
if (compileInterpretChoice == CompileInterpretChoice.AlwaysCompile)
{
return lambda.Compile();
}
// threshold is # of times the script must run before we decide to compile
// NeverCompile sets the threshold to int.MaxValue, so theoretically we might compile
// at some point, but it's very unlikely.
int threshold = (compileInterpretChoice == CompileInterpretChoice.NeverCompile) ? int.MaxValue : -1;
var deleg = new LightCompiler(threshold).CompileTop(lambda).CreateDelegate();
return (Action<FunctionContext>)deleg;
}
internal static object GetExpressionValue(ExpressionAst expressionAst, bool isTrustedInput, ExecutionContext context, IDictionary usingValues = null)
{
return GetExpressionValue(expressionAst, isTrustedInput, context, null, usingValues);
}
internal static object GetExpressionValue(ExpressionAst expressionAst, bool isTrustedInput, ExecutionContext context, SessionStateInternal sessionStateInternal, IDictionary usingValues = null)
{
Func<FunctionContext, object> lambda = null;
IScriptExtent[] sequencePoints = null;
Type localsTupleType = null;
return GetExpressionValue(expressionAst, isTrustedInput, context, sessionStateInternal, usingValues, ref lambda, ref sequencePoints, ref localsTupleType);
}
private static object GetExpressionValue(
ExpressionAst expressionAst,
bool isTrustedInput,
ExecutionContext context,
SessionStateInternal sessionStateInternal,
IDictionary usingValues,
ref Func<FunctionContext, object> lambda,
ref IScriptExtent[] sequencePoints,
ref Type localsTupleType)
{
object constantValue;
if (IsConstantValueVisitor.IsConstant(expressionAst, out constantValue))
{
return constantValue;
}
// If this isn't trusted input, then just return.
if (!isTrustedInput)
{
return null;
}
// Can't be exposed to untrusted input - exposing private variable names / etc. could be
// information disclosure.
var variableAst = expressionAst as VariableExpressionAst;
if (variableAst != null)
{
// We can avoid creating a lambda for the common case of a simple variable expression.
return VariableOps.GetVariableValue(variableAst.VariablePath, context, variableAst);
}
// Can't be exposed to untrusted input - invoking arbitrary code could result in remote code
// execution.
if (lambda == null)
{
lambda = (new Compiler()).CompileSingleExpression(expressionAst, out sequencePoints, out localsTupleType);
}
SessionStateInternal oldSessionState = context.EngineSessionState;
try
{
if (sessionStateInternal != null && context.EngineSessionState != sessionStateInternal)
{
// If we're running a function from a module, we need to evaluate the initializers in the
// module context, not the callers context...
context.EngineSessionState = sessionStateInternal;
}
var resultList = new List<object>();
var pipe = new Pipe(resultList);
try
{
var functionContext = new FunctionContext
{
_sequencePoints = sequencePoints,
_executionContext = context,
_file = expressionAst.Extent.File,
_outputPipe = pipe,
_localsTuple = MutableTuple.MakeTuple(localsTupleType, DottedLocalsNameIndexMap)
};
if (usingValues != null)
{
var boundParameters = new PSBoundParametersDictionary { ImplicitUsingParameters = usingValues };
functionContext._localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters, boundParameters, context);
}
var result = lambda(functionContext);
if (result == AutomationNull.Value)
{
return resultList.Count == 0 ? null : PipelineOps.PipelineResult(resultList);
}
return result;
}
catch (TargetInvocationException tie)
{
throw tie.InnerException;
}
}
catch (TerminateException)
{
// the debugger is terminating the execution; bubble up the exception
throw;
}
catch (FlowControlException)
{
// ignore break, continue and return exceptions
return null;
}
finally
{
context.EngineSessionState = oldSessionState;
}
}
private Func<FunctionContext, object> CompileSingleExpression(ExpressionAst expressionAst, out IScriptExtent[] sequencePoints, out Type localsTupleType)
{
Optimize = false;
_compilingSingleExpression = true;
var details = VariableAnalysis.AnalyzeExpression(expressionAst);
LocalVariablesTupleType = localsTupleType = details.Item1;
LocalVariablesParameter = Expression.Variable(LocalVariablesTupleType, "locals");
_returnTarget = Expression.Label(typeof(object), "returnTarget");
_loopTargets.Clear();
var exprs = new List<Expression>();
var temps = new List<ParameterExpression> { s_executionContextParameter, LocalVariablesParameter };
GenerateFunctionProlog(exprs, temps, null);
int index = AddSequencePoint(expressionAst.Extent);
exprs.Add(new UpdatePositionExpr(expressionAst.Extent, index, _debugSymbolDocument, checkBreakpoints: true));
var result = Compile(expressionAst).Cast(typeof(object));
exprs.Add(Expression.Label(_returnTarget, result));
var body = Expression.Block(new[] { s_executionContextParameter, LocalVariablesParameter }, exprs);
var parameters = new[] { s_functionContext };
sequencePoints = _sequencePoints.ToArray();
return Expression.Lambda<Func<FunctionContext, object>>(body, parameters).Compile();
}
private sealed class LoopGotoTargets
{
internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget continueLabel)
{
this.Label = label;
this.BreakLabel = breakLabel;
this.ContinueLabel = continueLabel;
}
internal string Label { get; }
internal LabelTarget ContinueLabel { get; }
internal LabelTarget BreakLabel { get; }
}
private LabelTarget _returnTarget;
private Expression<Action<FunctionContext>> _dynamicParamBlockLambda;
private Expression<Action<FunctionContext>> _beginBlockLambda;
private Expression<Action<FunctionContext>> _processBlockLambda;
private Expression<Action<FunctionContext>> _endBlockLambda;
private readonly List<LoopGotoTargets> _loopTargets = new List<LoopGotoTargets>();
private bool _generatingWhileOrDoLoop;
private enum CaptureAstContext
{
Condition,
Enumerable,
AssignmentWithResultPreservation,
AssignmentWithoutResultPreservation
}
private delegate void MergeRedirectExprs(List<Expression> exprs, List<Expression> finallyExprs);
private Expression CaptureAstResults(
Ast ast,
CaptureAstContext context,
MergeRedirectExprs generateRedirectExprs = null)
{
Expression result;
// We'll generate code like:
// try {
// oldPipe = funcContext.OutputPipe;
// resultList = new List<object>();
// resultListPipe = new Pipe(resultList);
// funcContext.OutputPipe = resultListPipe;
// <optionally add merge redirection expressions>
// expression...
// } finally {
// FlushPipe(oldPipe, resultList);
// funcContext.OutputPipe = oldPipe;
// }
//
var temps = new List<ParameterExpression>();
var exprs = new List<Expression>();
var catches = new List<CatchBlock>();
var finallyExprs = new List<Expression>();
var oldPipe = NewTemp(typeof(Pipe), "oldPipe");
var resultList = NewTemp(typeof(List<object>), "resultList");
temps.Add(resultList);
temps.Add(oldPipe);
exprs.Add(Expression.Assign(oldPipe, s_getCurrentPipe));
exprs.Add(Expression.Assign(resultList, Expression.New(CachedReflectionInfo.ObjectList_ctor)));
exprs.Add(Expression.Assign(s_getCurrentPipe, Expression.New(CachedReflectionInfo.Pipe_ctor, resultList)));
exprs.Add(Expression.Call(oldPipe, CachedReflectionInfo.Pipe_SetVariableListForTemporaryPipe, s_getCurrentPipe));
// Add merge redirection expressions if delegate is provided.
generateRedirectExprs?.Invoke(exprs, finallyExprs);
exprs.Add(Compile(ast));
switch (context)
{
case CaptureAstContext.AssignmentWithResultPreservation:
case CaptureAstContext.AssignmentWithoutResultPreservation:
result = Expression.Call(CachedReflectionInfo.PipelineOps_PipelineResult, resultList);
// Clear the temporary pipe in case of exception, if we are not required to preserve the results
if (context == CaptureAstContext.AssignmentWithoutResultPreservation)
{
var catchExprs = new List<Expression>
{
Expression.Call(CachedReflectionInfo.PipelineOps_ClearPipe, resultList),
Expression.Rethrow(),
Expression.Constant(null, typeof(object))
};
catches.Add(Expression.Catch(typeof(RuntimeException), Expression.Block(typeof(object), catchExprs)));
}
// PipelineResult might get skipped in some circumstances due to a FlowControlException thrown out, in which case
// we write to the oldPipe. This can happen in cases like:
// $(1;2;return 3)
finallyExprs.Add(Expression.Call(CachedReflectionInfo.PipelineOps_FlushPipe, oldPipe, resultList));
break;
case CaptureAstContext.Condition:
result = DynamicExpression.Dynamic(PSPipelineResultToBoolBinder.Get(), typeof(bool), resultList);
break;
case CaptureAstContext.Enumerable:
result = resultList;
break;
default:
throw new ArgumentOutOfRangeException(nameof(context));
}
finallyExprs.Add(Expression.Assign(s_getCurrentPipe, oldPipe));
exprs.Add(result);
if (catches.Count > 0)
{
return Expression.Block(
temps.ToArray(),
Expression.TryCatchFinally(
Expression.Block(exprs),
Expression.Block(finallyExprs),
catches.ToArray()));
}
return Expression.Block(
temps.ToArray(),
Expression.TryFinally(
Expression.Block(exprs),
Expression.Block(finallyExprs)));
}
private Expression CaptureStatementResultsHelper(
StatementAst stmt,
CaptureAstContext context,
MergeRedirectExprs generateRedirectExprs)
{
var commandExpressionAst = stmt as CommandExpressionAst;
if (commandExpressionAst != null)
{
if (commandExpressionAst.Redirections.Count > 0)
{
return GetRedirectedExpression(commandExpressionAst, captureForInput: true);
}
return Compile(commandExpressionAst.Expression);
}
var assignmentStatementAst = stmt as AssignmentStatementAst;
if (assignmentStatementAst != null)
{
var expr = Compile(assignmentStatementAst);
if (stmt.Parent is StatementBlockAst)
{
expr = Expression.Block(expr, ExpressionCache.Empty);
}
return expr;
}
var pipelineAst = stmt as PipelineAst;
// If it's a pipeline that isn't being backgrounded, try to optimize expression
if (pipelineAst != null && !pipelineAst.Background)
{
var expr = pipelineAst.GetPureExpression();
if (expr != null)
{
return Compile(expr);
}
}
return CaptureAstResults(stmt, context, generateRedirectExprs);
}
private Expression CaptureStatementResults(
StatementAst stmt,
CaptureAstContext context,
MergeRedirectExprs generateRedirectExprs = null)
{
var result = CaptureStatementResultsHelper(stmt, context, generateRedirectExprs);
// If we're generating code for a condition and the condition contains some command invocation,
// we want to be sure that $? is set to true even if the condition fails, e.g.:
// if (get-command foo -ea SilentlyContinue) { foo }
// $? # never $false here
// Many conditions don't invoke commands though, and in trivial empty loops, setting $? = $true
// does have a measurable impact, so only set $? = $true if the condition might change $? to $false.
// We do this after evaluating the condition so that you could do something like:
// if ((dir file1,file2 -ea SilentlyContinue) -and $?) { <# files both exist, otherwise $? would be $false if 0 or 1 files existed #> }
//
if (context == CaptureAstContext.Condition && AstSearcher.FindFirst(stmt, static ast => ast is CommandAst, searchNestedScriptBlocks: false) != null)
{
var tmp = NewTemp(result.Type, "condTmp");
result = Expression.Block(
new[] { tmp },
Expression.Assign(tmp, result),
s_setDollarQuestionToTrue,
tmp);
}
return result;
}
internal Expression CallAddPipe(Expression expr, Expression pipe)
{
if (!PSEnumerableBinder.IsStaticTypePossiblyEnumerable(expr.Type))
{
return Expression.Call(pipe, CachedReflectionInfo.Pipe_Add, expr.Cast(typeof(object)));
}
return DynamicExpression.Dynamic(PSPipeWriterBinder.Get(), typeof(void), expr, pipe, s_executionContextParameter);
}
public object VisitErrorStatement(ErrorStatementAst errorStatementAst)
{
return null;
}
public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst)
{
return ExpressionCache.Constant(1);
}
#region Script Blocks
public object VisitScriptBlock(ScriptBlockAst scriptBlockAst)
{
var funcDefn = scriptBlockAst.Parent as FunctionDefinitionAst;
var funcName = (funcDefn != null) ? funcDefn.Name : "<ScriptBlock>";
var rootForDefiningTypesAndUsings = scriptBlockAst.Find(static ast => ast is TypeDefinitionAst || ast is UsingStatementAst, true) != null
? scriptBlockAst
: null;
if (scriptBlockAst.DynamicParamBlock != null)
{
_dynamicParamBlockLambda = CompileNamedBlock(scriptBlockAst.DynamicParamBlock, funcName + "<DynamicParam>", rootForDefiningTypesAndUsings);
rootForDefiningTypesAndUsings = null;
}
// Skip param block - nothing to generate, defaults get generated when generating parameter metadata.
if (scriptBlockAst.BeginBlock != null)
{
_beginBlockLambda = CompileNamedBlock(scriptBlockAst.BeginBlock, funcName + "<Begin>", rootForDefiningTypesAndUsings);
rootForDefiningTypesAndUsings = null;
}
if (scriptBlockAst.ProcessBlock != null)
{
var processFuncName = funcName;
if (!scriptBlockAst.ProcessBlock.Unnamed)
{
processFuncName = funcName + "<Process>";
}
_processBlockLambda = CompileNamedBlock(scriptBlockAst.ProcessBlock, processFuncName, rootForDefiningTypesAndUsings);
rootForDefiningTypesAndUsings = null;
}
if (scriptBlockAst.EndBlock != null)
{
if (!scriptBlockAst.EndBlock.Unnamed)
{
funcName += "<End>";
}
_endBlockLambda = CompileNamedBlock(scriptBlockAst.EndBlock, funcName, rootForDefiningTypesAndUsings);
}
return null;
}
private Expression<Action<FunctionContext>> CompileNamedBlock(NamedBlockAst namedBlockAst, string funcName, ScriptBlockAst rootForDefiningTypes)
{
IScriptExtent entryExtent = null;
IScriptExtent exitExtent = null;
if (namedBlockAst.Unnamed)
{
// Get extent from the function or scriptblock expression parent, if any.
var scriptBlock = (ScriptBlockAst)namedBlockAst.Parent;
if (scriptBlock.Parent != null && scriptBlock.Extent is InternalScriptExtent)
{
// We must have curlies at the start/end.
var scriptExtent = (InternalScriptExtent)scriptBlock.Extent;
entryExtent = new InternalScriptExtent(scriptExtent.PositionHelper, scriptExtent.StartOffset, scriptExtent.StartOffset + 1);
exitExtent = new InternalScriptExtent(scriptExtent.PositionHelper, scriptExtent.EndOffset - 1, scriptExtent.EndOffset);
}
}
else
{
entryExtent = namedBlockAst.OpenCurlyExtent;
exitExtent = namedBlockAst.CloseCurlyExtent;
}
return CompileSingleLambda(namedBlockAst.Statements, namedBlockAst.Traps, funcName, entryExtent, exitExtent, rootForDefiningTypes);
}
private Tuple<Action<FunctionContext>, Type> CompileTrap(TrapStatementAst trap)
{
var compiler = new Compiler(_sequencePoints, _sequencePointIndexMap) { _compilingTrap = true };
string funcName = _currentFunctionName + "<trap>";
if (trap.TrapType != null)
{
funcName += "<" + trap.TrapType.TypeName.Name + ">";
}
// We generate code as though we're dotting the trap, but in reality we don't dot it,
// a new scope is always created. The code gen for dotting means we can avoid passing
// around the array of local variable names. We assume traps don't need to perform well,
// and that they rarely if ever actually create any local variables. We still do the
// variable analysis though because we must mark automatic variables like $_.
var analysis = VariableAnalysis.AnalyzeTrap(trap);
compiler.LocalVariablesTupleType = analysis.Item1;
compiler.LocalVariablesParameter = Expression.Variable(compiler.LocalVariablesTupleType, "locals");
var lambda = compiler.CompileSingleLambda(trap.Body.Statements, trap.Body.Traps, funcName, null, null, null);
return Tuple.Create(lambda.Compile(), compiler.LocalVariablesTupleType);
}
private Expression<Action<FunctionContext>> CompileSingleLambda(
ReadOnlyCollection<StatementAst> statements,
ReadOnlyCollection<TrapStatementAst> traps,
string funcName,
IScriptExtent entryExtent,
IScriptExtent exitExtent,
ScriptBlockAst rootForDefiningTypesAndUsings)
{
_currentFunctionName = funcName;
_loopTargets.Clear();
_returnTarget = Expression.Label("returnTarget");
var exprs = new List<Expression>();
var temps = new List<ParameterExpression>();
GenerateFunctionProlog(exprs, temps, entryExtent);
if (rootForDefiningTypesAndUsings != null)
{
GenerateTypesAndUsings(rootForDefiningTypesAndUsings, exprs);
}
var actualBodyExprs = new List<Expression>();
if (CompilingMemberFunction)
{
temps.Add(s_returnPipe);
}
CompileStatementListWithTraps(statements, traps, actualBodyExprs, temps);
exprs.AddRange(actualBodyExprs);
// We always add the return label even if it's unused - that way it doesn't matter what the last
// expression is in the body - the full body will always have void type.
exprs.Add(Expression.Label(_returnTarget));
GenerateFunctionEpilog(exprs, exitExtent);
temps.Add(LocalVariablesParameter);
Expression body = Expression.Block(temps, exprs);
// A return from a normal block is just that - a simple return (no exception).
// A return from a trap turns into an exception because the trap is compiled into a different lambda, yet
// the return from the trap must return from the function containing the trap. So we wrap the full
// body of regular begin/process/end blocks with a try/catch so a return from the trap returns
// to the right place. We can avoid also avoid generating the catch if we know there aren't any traps.
if (!_compilingTrap &&
((traps != null && traps.Count > 0)
|| statements.Any(static stmt => AstSearcher.Contains(stmt, static ast => ast is TrapStatementAst, searchNestedScriptBlocks: false))))
{
body = Expression.Block(
new[] { s_executionContextParameter },
Expression.TryCatchFinally(
body,
Expression.Call(
Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger),
CachedReflectionInfo.Debugger_ExitScriptFunction),
Expression.Catch(typeof(ReturnException), ExpressionCache.Empty)));
}
else
{
// Either no traps, or we're compiling a trap - either way don't catch the ReturnException.
body = Expression.Block(
new[] { s_executionContextParameter },
Expression.TryFinally(
body,
Expression.Call(
Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger),
CachedReflectionInfo.Debugger_ExitScriptFunction)));
}
return Expression.Lambda<Action<FunctionContext>>(body, funcName, new[] { s_functionContext });
}
private static void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List<Expression> exprs)
{
// We don't postpone load assemblies, import modules from 'using' to the moment, when enclosed scriptblock is executed.
// We do loading, when root of the script is compiled.
// This allow us to avoid creating 10 different classes in this situation:
// 1..10 | ForEach-Object { class C {} }
// But it's possible that we are loading something from the codepaths that we never execute.
// If Parent of rootForDefiningTypesAndUsings is not null, then we already defined all types, when Visit a parent ScriptBlock
if (rootForDefiningTypesAndUsings.Parent == null)
{
if (rootForDefiningTypesAndUsings.UsingStatements.Count > 0)
{
bool allUsingsAreNamespaces = rootForDefiningTypesAndUsings.UsingStatements.All(static us => us.UsingStatementKind == UsingStatementKind.Namespace);
GenerateLoadUsings(rootForDefiningTypesAndUsings.UsingStatements, allUsingsAreNamespaces, exprs);
}
TypeDefinitionAst[] typeAsts =
rootForDefiningTypesAndUsings.FindAll(static ast => ast is TypeDefinitionAst, true)
.Cast<TypeDefinitionAst>()
.ToArray();
if (typeAsts.Length > 0)
{
var assembly = DefinePowerShellTypes(rootForDefiningTypesAndUsings, typeAsts);
exprs.Add(
Expression.Call(
CachedReflectionInfo.TypeOps_SetAssemblyDefiningPSTypes,
s_functionContext,
Expression.Constant(assembly)));
exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_InitPowerShellTypesAtRuntime, Expression.Constant(typeAsts)));
}
}
Dictionary<string, TypeDefinitionAst> typesToAddToScope =
rootForDefiningTypesAndUsings.FindAll(static ast => ast is TypeDefinitionAst, false)
.Cast<TypeDefinitionAst>()
.ToDictionary(static type => type.Name);
if (typesToAddToScope.Count > 0)
{
exprs.Add(
Expression.Call(
CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope,
Expression.Constant(typesToAddToScope),
s_executionContextParameter));
}
}
/// <summary>
/// </summary>
/// <param name="usingStatements">Using statement asts.</param>
/// <param name="allUsingsAreNamespaces">This flag allow us some optimizations, if usings don't have assemblies and modules.</param>
/// <param name="exprs"></param>
internal static void GenerateLoadUsings(IEnumerable<UsingStatementAst> usingStatements, bool allUsingsAreNamespaces, List<Expression> exprs)
{
TypeResolutionState trs;
Dictionary<string, TypeDefinitionAst> typesToAdd = null;
if (allUsingsAreNamespaces)
{
trs = new TypeResolutionState(TypeOps.GetNamespacesForTypeResolutionState(usingStatements), null);
}
else
{
Assembly[] assemblies;
typesToAdd = LoadUsingsImpl(usingStatements, out assemblies);
trs = new TypeResolutionState(
TypeOps.GetNamespacesForTypeResolutionState(usingStatements),
assemblies);
}
exprs.Add(Expression.Call(
CachedReflectionInfo.TypeOps_SetCurrentTypeResolutionState,
Expression.Constant(trs), s_executionContextParameter));
if (typesToAdd != null && typesToAdd.Count > 0)
{
exprs.Add(Expression.Call(
CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope,
Expression.Constant(typesToAdd), s_executionContextParameter));
}
}
/// <summary>
/// Bake types and creates a dynamic assembly.
/// This method should be called only for rootAsts (i.e. script file root ScriptBlockAst).
/// </summary>
/// <param name="rootForDefiningTypes"></param>
/// <param name="typeAsts">Non-empty array of TypeDefinitionAst.</param>
/// <returns>Assembly with defined types.</returns>
internal static Assembly DefinePowerShellTypes(Ast rootForDefiningTypes, TypeDefinitionAst[] typeAsts)
{
// TODO(sevoroby): this Diagnostic is conceptually right.
// BUT It triggers, when we define type in an InitialSessionState and use it later in two different PowerShell instances.
// Diagnostics.Assert(typeAsts[0].Type == null, "We must not call DefinePowerShellTypes twice for the same TypeDefinitionAsts");
if (typeAsts[0].Type != null)
{
// We treat Type as a mutable buffer field and wipe it here to start definitions from scratch.
// I didn't find any real scenario when it can cause problems, except multi-threaded environment, which is rear and out-of-scope for now.
// Potentially, we can fix it with Ast.Copy() and rewiring ITypeName references for the whole tree.
foreach (var typeDefinitionAst in typeAsts)
{
typeDefinitionAst.Type = null;
}
}
// This is a short term solution - all error messages produced by creating the types should happen
// at parse time, not runtime.
var parser = new Parser();
var assembly = TypeDefiner.DefineTypes(parser, rootForDefiningTypes, typeAsts);
if (parser.ErrorList.Count > 0)
{
// wipe types, if there are any errors.
foreach (var typeDefinitionAst in typeAsts)
{
typeDefinitionAst.Type = null;
}
throw new ParseException(parser.ErrorList.ToArray());
}
return assembly;
}
private static Dictionary<string, TypeDefinitionAst> LoadUsingsImpl(IEnumerable<UsingStatementAst> usingAsts, out Assembly[] assemblies)
{
var asms = new List<Assembly>();
var types = new Dictionary<string, TypeDefinitionAst>(StringComparer.OrdinalIgnoreCase);
foreach (var usingStmt in usingAsts)
{
switch (usingStmt.UsingStatementKind)
{
case UsingStatementKind.Assembly:
asms.Add(LoadAssembly(usingStmt.Name.Value, usingStmt.Extent.File));
break;
case UsingStatementKind.Command:
break;
case UsingStatementKind.Module:
var module = LoadModule(usingStmt.ModuleInfo);
if (module != null)
{
if (module.ImplementingAssembly != null)
{
asms.Add(module.ImplementingAssembly);
}
var exportedTypes = module.GetExportedTypeDefinitions();
PopulateRuntimeTypes(usingStmt.ModuleInfo.GetExportedTypeDefinitions(), exportedTypes);
foreach (var nameTypePair in exportedTypes)
{
types[SymbolResolver.GetModuleQualifiedName(module.Name, nameTypePair.Key)] = nameTypePair.Value;
}
}
break;
case UsingStatementKind.Namespace:
break;
case UsingStatementKind.Type:
break;
default:
Diagnostics.Assert(false, "Unknown enum value " + usingStmt.UsingStatementKind + " for UsingStatementKind");
break;
}
}
assemblies = asms.ToArray();
return types;
}
private static void PopulateRuntimeTypes(ReadOnlyDictionary<string, TypeDefinitionAst> parseTimeTypes, ReadOnlyDictionary<string, TypeDefinitionAst> runtimeTypes)
{
foreach (KeyValuePair<string, TypeDefinitionAst> parseTypePair in parseTimeTypes)
{
// We only need to populate types, if ASTs are not reused. Otherwise it's already populated.
if (parseTypePair.Value.Type == null)
{
TypeDefinitionAst typeDefinitionAst;
if (!runtimeTypes.TryGetValue(parseTypePair.Key, out typeDefinitionAst))
{
throw InterpreterError.NewInterpreterException(
parseTypePair.Value,
typeof(RuntimeException),
parseTypePair.Value.Extent,
"TypeNotFound",
ParserStrings.TypeNotFound,
parseTypePair.Value.Name);
}
parseTypePair.Value.Type = typeDefinitionAst.Type;
}
}
}
private static Assembly LoadAssembly(string assemblyName, string scriptFileName)
{
var assemblyFileName = assemblyName;
Assembly assembly = null;
try
{
if (!string.IsNullOrEmpty(scriptFileName) && !Path.IsPathRooted(assemblyFileName))
{
assemblyFileName = Path.GetDirectoryName(scriptFileName) + "\\" + assemblyFileName;
}
#if !CORECLR
if (!File.Exists(assemblyFileName))
{
Microsoft.CodeAnalysis.GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName);
}
#endif
if (File.Exists(assemblyFileName))
{
assembly = Assembly.LoadFrom(assemblyFileName);
}
}
catch
{
}
if (assembly == null)
{
throw InterpreterError.NewInterpreterException(
assemblyName,
typeof(RuntimeException),
null,
"ErrorLoadingAssembly",
ParserStrings.ErrorLoadingAssembly,
assemblyName);
}
return assembly;
}
/// <summary>
/// Take module info of module that can be already loaded or not and loads it.
/// </summary>
/// <param name="originalModuleInfo">Module to load.</param>
/// <returns>Module info of the same module, but loaded.</returns>
private static PSModuleInfo LoadModule(PSModuleInfo originalModuleInfo)
{
// originalModuleInfo is created during parse time and may not contain [System.Type] types exported from the module.
// At this moment, we need to actually load the module and create types.
// We want to load **exactly the same module** as we used at parse time.
// And avoid the module resolution logic that can lead to another module (if something changed on the file system).
// So, we load the module by the file path, not by module specification (ModuleName, RequiredVersion).
var modulePath = originalModuleInfo.Path;
var commandInfo = new CmdletInfo("Import-Module", typeof(ImportModuleCommand));
var ps = PowerShell.Create(RunspaceMode.CurrentRunspace)
.AddCommand(commandInfo)
.AddParameter("Name", modulePath)
.AddParameter("PassThru");
var moduleInfo = ps.Invoke<PSModuleInfo>();
// It's possible that 'ps.HadErrors == true' while the error stream is empty. That would happen if
// one or more non-terminating errors happen when running the module script and ErrorAction is set
// to 'SilentlyContinue'. In such case, the errors would not be written to the error stream.
// It's OK to treat the module loading as successful in this case because the non-terminating errors
// are explicitly handled with 'SilentlyContinue' action, which means they don't block the loading.
if (ps.HadErrors && ps.Streams.Error.Count > 0)
{
var errorRecord = ps.Streams.Error[0];
throw InterpreterError.NewInterpreterException(
modulePath,
typeof(RuntimeException),
null,
errorRecord.FullyQualifiedErrorId,
errorRecord.ToString());
}
if (moduleInfo.Count == 1)
{
return moduleInfo[0];
}
else
{
Diagnostics.Assert(false, "We should load exactly one module from the provided original module");
return null;
}
}
private void GenerateFunctionProlog(List<Expression> exprs, List<ParameterExpression> temps, IScriptExtent entryExtent)
{
exprs.Add(Expression.Assign(
s_executionContextParameter,
Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__executionContext)));
exprs.Add(Expression.Assign(
LocalVariablesParameter,
Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__localsTuple).Cast(this.LocalVariablesTupleType)));
// Compiling a single expression (a default argument, or an locally evaluated argument in a ScriptBlock=>PowerShell conversion)
// does not support debugging, so skip calling EnterScriptFunction.
if (!_compilingSingleExpression)
{
exprs.Add(Expression.Assign(
Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__functionName),
Expression.Constant(_currentFunctionName)));
if (entryExtent != null)
{
int index = AddSequencePoint(entryExtent);
exprs.Add(new UpdatePositionExpr(entryExtent, index, _debugSymbolDocument, checkBreakpoints: false));
}
exprs.Add(
Expression.Call(
Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger),
CachedReflectionInfo.Debugger_EnterScriptFunction,
s_functionContext));
}
if (CompilingMemberFunction)
{
// Member functions don't write to the pipeline, they return values.
// Set the default pipe to the null pipe, but remember the pipe parameter
// so when we do compile the return statement, we can write to it's pipe.
exprs.Add(Expression.Assign(s_returnPipe, s_getCurrentPipe));
exprs.Add(Expression.Assign(s_getCurrentPipe, ExpressionCache.NullPipe));
Diagnostics.Assert(_memberFunctionType.Type != null, "Member function type should not be null");
var ourThis = NewTemp(_memberFunctionType.Type, "this");
temps.Add(ourThis);
exprs.Add(
Expression.Assign(
ourThis,
GetLocal(Array.IndexOf(SpecialVariables.AutomaticVariables, SpecialVariables.This)).Cast(_memberFunctionType.Type)));
switch (SpecialMemberFunctionType)
{
case SpecialMemberFunctionType.DefaultConstructor:
exprs.Add(InitializeMemberProperties(ourThis));
break;
case SpecialMemberFunctionType.StaticConstructor:
exprs.Add(InitializeMemberProperties(ourThis: null));
break;
}
}
}
private Expression InitializeMemberProperties(Expression ourThis)
{
var exprs = new List<Expression>();
bool wantStatic = ourThis == null;
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | (wantStatic ? BindingFlags.Static : BindingFlags.Instance);
foreach (var propertyMember in _memberFunctionType.Members.OfType<PropertyMemberAst>())
{
if (propertyMember.InitialValue == null || propertyMember.IsStatic != wantStatic)
{
continue;
}
var extent = propertyMember.InitialValue.Extent;
int index = AddSequencePoint(extent);
exprs.Add(new UpdatePositionExpr(extent, index, _debugSymbolDocument, checkBreakpoints: false));
var property = _memberFunctionType.Type.GetProperty(propertyMember.Name, bindingFlags);
exprs.Add(
Expression.Assign(
Expression.Property(ourThis, property),
Compile(propertyMember.InitialValue).Convert(property.PropertyType)));
}
exprs.Add(ExpressionCache.Empty);
return Expression.TryCatch(Expression.Block(exprs), s_stmtCatchHandlers);
}
private void GenerateFunctionEpilog(List<Expression> exprs, IScriptExtent exitExtent)
{
if (exitExtent != null)
{
exprs.Add(UpdatePosition(new SequencePointAst(exitExtent)));
}
}
public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
{
Diagnostics.Assert(false, "Nothing to generate for a type constraint");
return null;
}
public object VisitAttribute(AttributeAst attributeAst)
{
Diagnostics.Assert(false, "Nothing to generate for an attribute");
return null;
}
public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst)
{
Diagnostics.Assert(false, "Nothing to generate for a named attribute argument");
return null;
}
public object VisitParameter(ParameterAst parameterAst)
{
Diagnostics.Assert(false, "Nothing to generate for a parameter");
return null;
}
public object VisitParamBlock(ParamBlockAst paramBlockAst)
{
Diagnostics.Assert(false, "Nothing to generate for a parameter block");
return null;
}
public object VisitNamedBlock(NamedBlockAst namedBlockAst)
{
Diagnostics.Assert(false, "NamedBlockAst is handled specially, not via the visitor.");
return null;
}
public object VisitStatementBlock(StatementBlockAst statementBlockAst)
{
var exprs = new List<Expression>();
var temps = new List<ParameterExpression>();
CompileStatementListWithTraps(statementBlockAst.Statements, statementBlockAst.Traps, exprs, temps);
if (exprs.Count == 0)
{
exprs.Add(ExpressionCache.Empty);
}
return Expression.Block(typeof(void), temps, exprs);
}
private int _trapNestingCount;
private void CompileStatementListWithTraps(
ReadOnlyCollection<StatementAst> statements,
ReadOnlyCollection<TrapStatementAst> traps,
List<Expression> exprs,
List<ParameterExpression> temps)
{
if (statements.Count == 0)
{
exprs.Add(ExpressionCache.Empty);
return;
}
var originalExprs = exprs;
Expression handlerInScope;
ParameterExpression oldActiveHandler;
ParameterExpression trapHandlersPushed;
if (traps != null)
{
// If the statement block has any traps, we'll generate code like:
// try {
// oldActiveHandler = executionContext.PropagateExceptionsToEnclosingStatementBlock;
// executionContext.PropagateExceptionsToEnclosingStatementBlock = true;
// functionContext.PushTrapHandlers(
// new Type[] { trapTypes },
// new Action<FunctionContext>[] { trapDelegates },
// new Type[] { trapLocalTupleTypes });
// trapHandlersPushed = true
//
// statements
// } finally {
// executionContext.PropagateExceptionsToEnclosingStatementBlock = oldActiveHandler;
// if (trapHandlersPushed) {
// functionContext.PopTrapHandlers()
// }
// }
//
// We use a runtime check on popping because in some rare cases, PushTrapHandlers might not
// get called (e.g. if a trap handler specifies a type that doesn't exist, like trap [baddtype]{}).
// We don't want to pop if we didn't push.
exprs = new List<Expression>();
handlerInScope = Expression.Property(
s_executionContextParameter,
CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock);
oldActiveHandler = NewTemp(typeof(bool), "oldActiveHandler");
exprs.Add(Expression.Assign(oldActiveHandler, handlerInScope));
exprs.Add(Expression.Assign(handlerInScope, ExpressionCache.Constant(true)));
var trapTypes = new List<Expression>();
var trapDelegates = new List<Action<FunctionContext>>();
var trapTupleType = new List<Type>();
for (int index = 0; index < traps.Count; index++)
{
var trap = traps[index];
trapTypes.Add(trap.TrapType != null
? CompileTypeName(trap.TrapType.TypeName, trap.TrapType.Extent)
: ExpressionCache.CatchAllType);
var tuple = CompileTrap(trap);
trapDelegates.Add(tuple.Item1);
trapTupleType.Add(tuple.Item2);
}
exprs.Add(Expression.Call(
s_functionContext, CachedReflectionInfo.FunctionContext_PushTrapHandlers,
Expression.NewArrayInit(typeof(Type), trapTypes),
Expression.Constant(trapDelegates.ToArray()),
Expression.Constant(trapTupleType.ToArray())));
trapHandlersPushed = NewTemp(typeof(bool), "trapHandlersPushed");
exprs.Add(Expression.Assign(trapHandlersPushed, ExpressionCache.Constant(true)));
_trapNestingCount += 1;
}
else
{
oldActiveHandler = null;
handlerInScope = null;
trapHandlersPushed = null;
if (_trapNestingCount > 0)
{
// If this statement block has no traps, but a parent block does, we need to make sure that process the
// trap at the correct level in case we continue. For example:
// trap { continue }
// if (1) {
// throw 1
// "Shouldn't continue here"
// }
// "Should continue here"
// In this example, the trap just continues, but we want to continue after the 'if' statement, not after
// the 'throw' statement.
// We push null onto the active trap handlers to let ExceptionHandlingOps.CheckActionPreference know it
// shouldn't process traps (but should still query the user if appropriate), and just rethrow so we can
// unwind to the block with the trap.
exprs = new List<Expression>();
exprs.Add(Expression.Call(
s_functionContext,
CachedReflectionInfo.FunctionContext_PushTrapHandlers,
ExpressionCache.NullTypeArray,
ExpressionCache.NullDelegateArray,
ExpressionCache.NullTypeArray));
trapHandlersPushed = NewTemp(typeof(bool), "trapHandlersPushed");
exprs.Add(Expression.Assign(trapHandlersPushed, ExpressionCache.Constant(true)));
}
}
_stmtCount += statements.Count;
// If there is a single statement, we just wrap it in try/catch (to handle traps and/or prompting based on
// $ErrorActionPreference.
//
// If there are multiple statements, we could wrap each statement in try/catch, but it's more efficient
// to have a single try/catch around the entire block.
//
// The interpreter handles a large number or try/catches fine, but the JIT fails miserably because it uses
// an exponential algorithm.
//
// Because a trap or user prompting can have us stop the erroneous statement, but continue to the next
// statement, we need to generate code like this:
//
// dispatchIndex = 0;
// DispatchNextStatementTarget:
// try {
// switch (dispatchIndex) {
// case 0: goto L0;
// case 1: goto L1;
// case 2: goto L2;
// }
// L0: dispatchIndex = 1;
// statement1;
// L1: dispatchIndex = 2;
// statement2;
// } catch (FlowControlException) {
// throw;
// } catch (Exception e) {
// ExceptionHandlingOps.CheckActionPreference(functionContext, e);
// goto DispatchNextStatementTarget;
// }
// L2:
//
// This approach makes JIT possible (but still slow) for large functions and it speeds up the light
// compile (interpreter compile) by about 80%.
if (statements.Count == 1)
{
var exprList = new List<Expression>(3);
CompileTrappableExpression(exprList, statements[0]);
exprList.Add(ExpressionCache.Empty);
var expr = Expression.TryCatch(Expression.Block(exprList), s_stmtCatchHandlers);
exprs.Add(expr);
}
else
{
var switchCases = new SwitchCase[statements.Count + 1];
var dispatchTargets = new LabelTarget[statements.Count + 1];
for (int i = 0; i <= statements.Count; i++)
{
dispatchTargets[i] = Expression.Label();
switchCases[i] = Expression.SwitchCase(
Expression.Goto(dispatchTargets[i]),
ExpressionCache.Constant(i));
}
var dispatchIndex = Expression.Variable(typeof(int), "stmt");
temps.Add(dispatchIndex);
exprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(0)));
var dispatchNextStatementTarget = Expression.Label();
exprs.Add(Expression.Label(dispatchNextStatementTarget));
var tryBodyExprs = new List<Expression>();
tryBodyExprs.Add(Expression.Switch(dispatchIndex, switchCases));
for (int i = 0; i < statements.Count; i++)
{
tryBodyExprs.Add(Expression.Label(dispatchTargets[i]));
tryBodyExprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(i + 1)));
CompileTrappableExpression(tryBodyExprs, statements[i]);
}
tryBodyExprs.Add(ExpressionCache.Empty);
var exception = Expression.Variable(typeof(Exception), "exception");
var callCheckActionPreference = Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference,
Compiler.s_functionContext,
exception);
var catchAll = Expression.Catch(
exception,
Expression.Block(
callCheckActionPreference,
Expression.Goto(dispatchNextStatementTarget)));
var expr = Expression.TryCatch(
Expression.Block(tryBodyExprs),
new CatchBlock[] { s_catchFlowControl, catchAll });
exprs.Add(expr);
exprs.Add(Expression.Label(dispatchTargets[statements.Count]));
}
if (_trapNestingCount > 0)
{
var parameterExprs = new List<ParameterExpression>();
var finallyBlockExprs = new List<Expression>();
if (oldActiveHandler != null)
{
parameterExprs.Add(oldActiveHandler);
finallyBlockExprs.Add(Expression.Assign(handlerInScope, oldActiveHandler));
}
parameterExprs.Add(trapHandlersPushed);
finallyBlockExprs.Add(
Expression.IfThen(trapHandlersPushed, Expression.Call(s_functionContext, CachedReflectionInfo.FunctionContext_PopTrapHandlers)));
originalExprs.Add(
Expression.Block(parameterExprs, Expression.TryFinally(Expression.Block(exprs), Expression.Block(finallyBlockExprs))));
}
if (traps != null)
{
_trapNestingCount -= 1;
}
}
private void CompileTrappableExpression(List<Expression> exprList, StatementAst stmt)
{
var expr = Compile(stmt);
exprList.Add(expr);
if (ShouldSetExecutionStatusToSuccess(stmt))
{
exprList.Add(s_setDollarQuestionToTrue);
}
}
/// <summary>
/// Determines whether a statement must have an explicit setting
/// for $? = $true after it by the compiler.
/// </summary>
/// <param name="statementAst">The statement to examine.</param>
/// <returns>True is the compiler should add the success setting, false otherwise.</returns>
private bool ShouldSetExecutionStatusToSuccess(StatementAst statementAst)
{
// Simple overload fan out
switch (statementAst)
{
case PipelineAst pipelineAst:
return ShouldSetExecutionStatusToSuccess(pipelineAst);
case AssignmentStatementAst assignmentStatementAst:
return ShouldSetExecutionStatusToSuccess(assignmentStatementAst);
default:
return false;
}
}
/// <summary>
/// Determines whether a pipeline must have an explicit setting
/// for $? = $true after it by the compiler.
/// </summary>
/// <param name="pipelineAst">The pipeline to examine.</param>
/// <returns>True is the compiler should add the success setting, false otherwise.</returns>
private bool ShouldSetExecutionStatusToSuccess(PipelineAst pipelineAst)
{
ExpressionAst expressionAst = pipelineAst.GetPureExpression();
// If the pipeline is not a simple expression, it will set $?
if (expressionAst == null)
{
return false;
}
// Expressions may still set $? themselves, so dig deeper
return ShouldSetExecutionStatusToSuccess(expressionAst);
}
/// <summary>
/// Determines whether an assignment statement must have an explicit setting
/// for $? = $true after it by the compiler.
/// </summary>
/// <param name="assignmentStatementAst">The assignment statement to examine.</param>
/// <returns>True is the compiler should add the success setting, false otherwise.</returns>
private bool ShouldSetExecutionStatusToSuccess(AssignmentStatementAst assignmentStatementAst)
{
// Get right-most RHS in cases like $x = $y = <expr>
StatementAst innerRhsStatementAst = assignmentStatementAst.Right;
while (innerRhsStatementAst is AssignmentStatementAst rhsAssignmentAst)
{
innerRhsStatementAst = rhsAssignmentAst.Right;
}
// Simple assignments to pure expressions may need $? set, so examine the RHS statement for pure expressions
switch (innerRhsStatementAst)
{
case CommandExpressionAst commandExpression:
return ShouldSetExecutionStatusToSuccess(commandExpression.Expression);
case PipelineAst rhsPipelineAst:
return ShouldSetExecutionStatusToSuccess(rhsPipelineAst);
default:
return false;
}
}
/// <summary>
/// Determines whether an expression in a statement must have an explicit setting
/// for $? = $true after it by the compiler.
/// </summary>
/// <param name="expressionAst">The expression to examine.</param>
/// <returns>True is the compiler should add the success setting, false otherwise.</returns>
private bool ShouldSetExecutionStatusToSuccess(ExpressionAst expressionAst)
{
switch (expressionAst)
{
case ParenExpressionAst parenExpression:
// Pipelines in paren expressions that are just pure expressions will need $? set
// e.g. ("Hi"), vs (Test-Path ./here.txt)
return ShouldSetExecutionStatusToSuccess(parenExpression.Pipeline);
case SubExpressionAst subExpressionAst:
// Subexpressions generally set $? since they encapsulate a statement block
// But $() requires an explicit setting
return subExpressionAst.SubExpression.Statements.Count == 0;
case ArrayExpressionAst arrayExpressionAst:
// ArrayExpressionAsts and SubExpressionAsts must be treated differently,
// since they are optimised for a single expression differently.
// A SubExpressionAst with a single expression in it has the $? = $true added,
// but the optimisation drills deeper for ArrayExpressionAsts,
// meaning we must inspect the expression itself in these cases
switch (arrayExpressionAst.SubExpression.Statements.Count)
{
case 0:
// @() needs $? set
return true;
case 1:
// Single expressions with a trap are handled as statements
// For example: @(trap { continue } "Value")
if (arrayExpressionAst.SubExpression.Traps != null)
{
return false;
}
// Pure, single statement expressions need $? set
// For example @("One") and @("One", "Two")
return ShouldSetExecutionStatusToSuccess(arrayExpressionAst.SubExpression.Statements[0]);
default:
// Arrays with multiple statements in them will have $? set
return false;
}
default:
return true;
}
}
#endregion Script Blocks
#region Statements
public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)
{
return ExpressionCache.Empty;
}
public object VisitPropertyMember(PropertyMemberAst propertyMemberAst)
{
return null;
}
public object VisitFunctionMember(FunctionMemberAst functionMemberAst)
{
return null;
}
public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
{
var target = CompileExpressionOperand(baseCtorInvokeMemberExpressionAst.Expression);
var args = CompileInvocationArguments(baseCtorInvokeMemberExpressionAst.Arguments);
var baseCtorCallConstraints = GetInvokeMemberConstraints(baseCtorInvokeMemberExpressionAst);
return InvokeBaseCtorMethod(baseCtorCallConstraints, target, args);
}
public object VisitUsingStatement(UsingStatementAst usingStatementAst)
{
return ExpressionCache.Empty;
}
public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationAst)
{
return this.VisitPipeline(configurationAst.GenerateSetItemPipelineAst());
}
public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst)
{
if (dynamicKeywordAst.Keyword.MetaStatement)
{
return Expression.Empty();
}
return this.VisitPipeline(dynamicKeywordAst.GenerateCommandCallPipelineAst());
}
public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
{
return Expression.Call(
CachedReflectionInfo.FunctionOps_DefineFunction,
s_executionContextParameter,
Expression.Constant(functionDefinitionAst),
Expression.Constant(new ScriptBlockExpressionWrapper(functionDefinitionAst)));
}
public object VisitIfStatement(IfStatementAst ifStmtAst)
{
int clauseCount = ifStmtAst.Clauses.Count;
var exprs = new Tuple<Expression, Expression>[clauseCount];
for (int i = 0; i < clauseCount; ++i)
{
IfClause ifClause = ifStmtAst.Clauses[i];
var cond = UpdatePositionForInitializerOrCondition(
ifClause.Item1,
CaptureStatementResults(ifClause.Item1, CaptureAstContext.Condition).Convert(typeof(bool)));
var body = Compile(ifClause.Item2);
exprs[i] = Tuple.Create(cond, body);
}
Expression elseExpr = null;
if (ifStmtAst.ElseClause != null)
{
elseExpr = Compile(ifStmtAst.ElseClause);
}
Expression result = null;
for (int i = clauseCount - 1; i >= 0; --i)
{
var cond = exprs[i].Item1;
var body = exprs[i].Item2;
if (elseExpr != null)
{
result = elseExpr = Expression.IfThenElse(cond, body, elseExpr);
}
else
{
result = elseExpr = Expression.IfThen(cond, body);
}
}
return result;
}
public object VisitTrap(TrapStatementAst trapStatementAst)
{
Diagnostics.Assert(false, "Traps are not visited directly.");
return null;
}
public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
{
return CompileAssignment(assignmentStatementAst);
}
private Expression CompileAssignment(
AssignmentStatementAst assignmentStatementAst,
MergeRedirectExprs generateRedirectExprs = null)
{
var arrayLHS = assignmentStatementAst.Left as ArrayLiteralAst;
var parenExpressionAst = assignmentStatementAst.Left as ParenExpressionAst;
if (parenExpressionAst != null)
{
arrayLHS = parenExpressionAst.Pipeline.GetPureExpression() as ArrayLiteralAst;
}
// If assigning to an array, then we prefer an enumerable result because we use an IList
// to generate the assignments, no sense in converting to object[], or worse, returning a
// single object.
// We should not preserve the partial output if exception is thrown when evaluating right-hand-side expression.
Expression rightExpr = CaptureStatementResults(
assignmentStatementAst.Right,
arrayLHS != null ? CaptureAstContext.Enumerable : CaptureAstContext.AssignmentWithoutResultPreservation,
generateRedirectExprs);
if (arrayLHS != null)
{
rightExpr = DynamicExpression.Dynamic(PSArrayAssignmentRHSBinder.Get(arrayLHS.Elements.Count), typeof(IList), rightExpr);
}
var exprs = new List<Expression>
{
// Set current position in case of errors.
UpdatePosition(assignmentStatementAst),
ReduceAssignment(
(ISupportsAssignment)assignmentStatementAst.Left,
assignmentStatementAst.Operator,
rightExpr)
};
return Expression.Block(exprs);
}
public object VisitPipelineChain(PipelineChainAst pipelineChainAst)
{
// If the statement chain is backgrounded,
// we defer that to the background operation call
if (pipelineChainAst.Background)
{
return Expression.Call(
CachedReflectionInfo.PipelineOps_InvokePipelineInBackground,
Expression.Constant(pipelineChainAst),
s_functionContext);
}
// We want to generate code like:
//
// dispatchIndex = 0;
// DispatchNextStatementTarget:
// try {
// switch (dispatchIndex) {
// case 0: goto L0;
// case 1: goto L1;
// case 2: goto L2;
// }
// L0: dispatchIndex = 1;
// pipeline1;
// L1: dispatchIndex = 2;
// if ($?) pipeline2;
// L2: dispatchIndex = 3;
// if ($?) pipeline3;
// ...
// } catch (FlowControlException) {
// throw;
// } catch (Exception e) {
// ExceptionHandlingOps.CheckActionPreference(functionContext, e);
// goto DispatchNextStatementTarget;
// }
// LN:
//
// Note that we deliberately do not push trap handlers
// so that those can be handled by the enclosing statement block instead.
var exprs = new List<Expression>();
// A pipeline chain is left-hand-side deep,
// so to compile from left to right, we need to start from the leaf
// and roll back up to the top, being the right-most element in the chain
PipelineChainAst currentChain = pipelineChainAst;
while (currentChain.LhsPipelineChain is PipelineChainAst lhsPipelineChain)
{
currentChain = lhsPipelineChain;
}
// int chainIndex = 0;
ParameterExpression dispatchIndex = Expression.Variable(typeof(int), nameof(dispatchIndex));
var temps = new ParameterExpression[] { dispatchIndex };
exprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(0)));
// DispatchNextTargetStatement:
LabelTarget dispatchNextStatementTargetLabel = Expression.Label();
exprs.Add(Expression.Label(dispatchNextStatementTargetLabel));
// try statement body
var switchCases = new List<SwitchCase>();
var dispatchTargets = new List<LabelTarget>();
var tryBodyExprs = new List<Expression>()
{
null, // Add a slot for the inital switch/case that we'll come back to
};
// L0: dispatchIndex = 1; pipeline1
LabelTarget label0 = Expression.Label();
dispatchTargets.Add(label0);
switchCases.Add(
Expression.SwitchCase(
Expression.Goto(label0),
ExpressionCache.Constant(0)));
tryBodyExprs.Add(Expression.Label(label0));
tryBodyExprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(1)));
tryBodyExprs.Add(CompilePipelineChainElement((PipelineAst)currentChain.LhsPipelineChain));
// Remainder of try statement body
// L1: dispatchIndex = 2; if ($?) pipeline2;
// ...
int chainIndex = 1;
do
{
// Record label and switch case for later use
LabelTarget currentLabel = Expression.Label();
dispatchTargets.Add(currentLabel);
switchCases.Add(
Expression.SwitchCase(
Expression.Goto(currentLabel),
ExpressionCache.Constant(chainIndex)));
// Add label and dispatchIndex for current pipeline
tryBodyExprs.Add(Expression.Label(currentLabel));
tryBodyExprs.Add(
Expression.Assign(
dispatchIndex,
ExpressionCache.Constant(chainIndex + 1)));
// Increment chain index for next iteration
chainIndex++;
Diagnostics.Assert(
currentChain.Operator == TokenKind.AndAnd || currentChain.Operator == TokenKind.OrOr,
"Chain operators must be either && or ||");
Expression dollarQuestionCheck = currentChain.Operator == TokenKind.AndAnd
? s_getDollarQuestion
: s_notDollarQuestion;
tryBodyExprs.Add(Expression.IfThen(dollarQuestionCheck, CompilePipelineChainElement(currentChain.RhsPipeline)));
currentChain = currentChain.Parent as PipelineChainAst;
}
while (currentChain != null);
// Add empty expression to make the block value void
tryBodyExprs.Add(ExpressionCache.Empty);
// Create the final label that follows the entire try/catch
LabelTarget afterLabel = Expression.Label();
switchCases.Add(
Expression.SwitchCase(
Expression.Goto(afterLabel),
ExpressionCache.Constant(chainIndex)));
// Now set the switch/case that belongs at the top
tryBodyExprs[0] = Expression.Switch(dispatchIndex, switchCases.ToArray());
// Create the catch block for flow control and action preference
ParameterExpression exception = Expression.Variable(typeof(Exception), nameof(exception));
MethodCallExpression callCheckActionPreference = Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference,
Compiler.s_functionContext,
exception);
CatchBlock catchAll = Expression.Catch(
exception,
Expression.Block(
callCheckActionPreference,
Expression.Goto(dispatchNextStatementTargetLabel)));
TryExpression expr = Expression.TryCatch(
Expression.Block(tryBodyExprs),
new CatchBlock[] { s_catchFlowControl, catchAll });
exprs.Add(expr);
exprs.Add(Expression.Label(afterLabel));
BlockExpression fullyExpandedBlock = Expression.Block(typeof(void), temps, exprs);
return fullyExpandedBlock;
}
/// <summary>
/// Compile a pipeline as an element in a pipeline chain.
/// Needed since pure expressions won't set $? after them.
/// <seealso cref="CompileTrappableExpression"/> which does something similar.
/// </summary>
/// <param name="pipelineAst">The pipeline in the pipeline chain to compile to an expression.</param>
/// <returns>The compiled expression to execute the pipeline.</returns>
private Expression CompilePipelineChainElement(PipelineAst pipelineAst)
{
if (ShouldSetExecutionStatusToSuccess(pipelineAst))
{
return Expression.Block(Compile(pipelineAst), s_setDollarQuestionToTrue);
}
return Compile(pipelineAst);
}
public object VisitPipeline(PipelineAst pipelineAst)
{
var temps = new List<ParameterExpression>();
var exprs = new List<Expression>();
if (pipelineAst.Parent is not AssignmentStatementAst && pipelineAst.Parent is not ParenExpressionAst)
{
// If the parent is an assignment, we've already added a sequence point, don't add another.
exprs.Add(UpdatePosition(pipelineAst));
}
if (pipelineAst.Background)
{
Expression invokeBackgroundPipe = Expression.Call(
CachedReflectionInfo.PipelineOps_InvokePipelineInBackground,
Expression.Constant(pipelineAst),
s_functionContext);
exprs.Add(invokeBackgroundPipe);
}
else
{
var pipeElements = pipelineAst.PipelineElements;
var firstCommandExpr = pipeElements[0] as CommandExpressionAst;
if (firstCommandExpr != null && pipeElements.Count == 1)
{
if (firstCommandExpr.Redirections.Count > 0)
{
exprs.Add(GetRedirectedExpression(firstCommandExpr, captureForInput: false));
}
else
{
exprs.Add(Compile(firstCommandExpr));
}
}
else
{
Expression input;
int i, commandsInPipe;
if (firstCommandExpr != null)
{
if (firstCommandExpr.Redirections.Count > 0)
{
input = GetRedirectedExpression(firstCommandExpr, captureForInput: true);
}
else
{
input = GetRangeEnumerator(firstCommandExpr.Expression) ??
Compile(firstCommandExpr.Expression);
}
if (input.Type == typeof(void))
{
input = Expression.Block(input, ExpressionCache.AutomationNullConstant);
}
i = 1;
commandsInPipe = pipeElements.Count - 1;
}
else
{
// Compiled code normally never sees AutomationNull. We use that value
// here so that we can tell the difference b/w $null and no input when
// starting the pipeline, in other words, PipelineOps.InvokePipe will
// not pass this value to the pipe.
input = ExpressionCache.AutomationNullConstant;
i = 0;
commandsInPipe = pipeElements.Count;
}
Expression[] pipelineExprs = new Expression[commandsInPipe];
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
var commandRedirections = new object[commandsInPipe];
for (int j = 0; i < pipeElements.Count; ++i, ++j)
{
var pipeElement = pipeElements[i];
pipelineExprs[j] = Compile(pipeElement);
commandRedirections[j] = GetCommandRedirections(pipeElement);
pipeElementAsts[j] = pipeElement;
}
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
// one dimension because each command may have multiple redirections. Here we create the array for
// each command in the pipe, either a compile time constant or created at runtime if necessary.
Expression redirectionExpr;
if (commandRedirections.Any(static r => r is Expression))
{
// If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it,
// in which case we must create the array at runtime
redirectionExpr =
Expression.NewArrayInit(
typeof(CommandRedirection[]),
commandRedirections.Select(static r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[]))));
}
else if (commandRedirections.Any(static r => r != null))
{
// There were redirections, but all were compile time constant, so build the array at compile time.
redirectionExpr =
Expression.Constant(commandRedirections.Map(static r => r as CommandRedirection[]));
}
else
{
// No redirections.
redirectionExpr = ExpressionCache.NullCommandRedirections;
}
if (firstCommandExpr != null)
{
var inputTemp = Expression.Variable(input.Type);
temps.Add(inputTemp);
exprs.Add(Expression.Assign(inputTemp, input));
input = inputTemp;
}
Expression invokePipe = Expression.Call(
CachedReflectionInfo.PipelineOps_InvokePipeline,
input.Cast(typeof(object)),
firstCommandExpr != null ? ExpressionCache.FalseConstant : ExpressionCache.TrueConstant,
Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs),
Expression.Constant(pipeElementAsts),
redirectionExpr,
s_functionContext);
exprs.Add(invokePipe);
}
}
return Expression.Block(temps, exprs);
}
private object GetCommandRedirections(CommandBaseAst command)
{
int count = command.Redirections.Count;
if (count == 0)
{
return null;
}
// Most redirections will be instances of CommandRedirection, but non-constant filenames
// will generated a Linq.Expression, so we store objects.
object[] compiledRedirections = new object[count];
for (int i = 0; i < count; ++i)
{
compiledRedirections[i] = command.Redirections[i].Accept(this);
}
// If there were any non-constant expressions, we must generate the array at runtime.
if (compiledRedirections.Any(static r => r is Expression))
{
return Expression.NewArrayInit(
typeof(CommandRedirection),
compiledRedirections.Select(static r => (r as Expression) ?? Expression.Constant(r)));
}
// Otherwise, we can use a compile time constant array.
return compiledRedirections.Map(static r => (CommandRedirection)r);
}
// A redirected expression requires extra work because there is no CommandProcessor or PipelineProcessor
// created to pass the redirections to, so we must change the redirections in the ExecutionContext
// directly.
private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, bool captureForInput)
{
// Generate code like:
//
// try {
// oldPipe = funcContext.OutputPipe;
// funcContext.OutputPipe = outputFileRedirection.BindForExpression(executionContext);
// tmp = nonOutputFileRedirection.BindForExpression(executionContext);
// tmp1 = mergingRedirection.BindForExpression(executionContext, funcContext.OutputPipe);
// funcContext.OutputPipe.Add(expression...);
// } finally {
// nonOutputFileRedirection.UnbindForExpression(tmp);
// mergingRedirection.UnbindForExpression(tmp1);
// nonOutputFileRedirection.Dispose();
// outputFileRedirection.Dispose();
// funcContext.OutputPipe = oldPipe;
// }
//
// In the above psuedo-code, any of {outputFileRedirection, nonOutputFileRedirection, mergingRedirection} may
// not exist, but the order is preserved, so that file redirections go before merging redirections (so that
// funcContext.OutputPipe has the correct value when setting up merging.)
//
var exprs = new List<Expression>();
var temps = new List<ParameterExpression>();
var finallyExprs = new List<Expression>();
// For the output stream, we change funcContext.OutputPipe so all output goes to the file.
// Currently output can only be redirected to a file stream.
bool outputRedirected =
commandExpr.Redirections.Any(static r => r is FileRedirectionAst &&
(r.FromStream == RedirectionStream.Output || r.FromStream == RedirectionStream.All));
ParameterExpression resultList = null;
ParameterExpression oldPipe = null;
var subExpr = commandExpr.Expression as SubExpressionAst;
if (subExpr != null && captureForInput)
{
oldPipe = NewTemp(typeof(Pipe), "oldPipe");
resultList = NewTemp(typeof(List<object>), "resultList");
temps.Add(resultList);
temps.Add(oldPipe);
exprs.Add(Expression.Assign(oldPipe, s_getCurrentPipe));
exprs.Add(Expression.Assign(resultList, Expression.New(CachedReflectionInfo.ObjectList_ctor)));
exprs.Add(Expression.Assign(s_getCurrentPipe, Expression.New(CachedReflectionInfo.Pipe_ctor, resultList)));
exprs.Add(Expression.Call(oldPipe, CachedReflectionInfo.Pipe_SetVariableListForTemporaryPipe, s_getCurrentPipe));
}
List<Expression> extraFileRedirectExprs = null;
// We must generate the code for output redirection to a file before any merging redirections
// because merging redirections will use funcContext.OutputPipe as the value to merge to, so defer merging
// redirections until file redirections are done.
foreach (var fileRedirectionAst in commandExpr.Redirections.OfType<FileRedirectionAst>())
{
// This will simply return a Linq.Expression representing the redirection.
var compiledRedirection = VisitFileRedirection(fileRedirectionAst);
if (extraFileRedirectExprs == null)
{
extraFileRedirectExprs = new List<Expression>(commandExpr.Redirections.Count);
}
// Hold the current 'FileRedirection' instance for later use
var redirectionExpr = NewTemp(typeof(FileRedirection), "fileRedirection");
temps.Add(redirectionExpr);
exprs.Add(Expression.Assign(redirectionExpr, (Expression)compiledRedirection));
// We must save the old streams so they can be restored later.
var savedPipes = NewTemp(typeof(Pipe[]), "savedPipes");
temps.Add(savedPipes);
exprs.Add(Expression.Assign(
savedPipes,
Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_BindForExpression, s_functionContext)));
// We need to call 'DoComplete' on the file redirection pipeline processor after writing the stream output to redirection pipe,
// so that the 'EndProcessing' method of 'Out-File' would be called as expected.
// Expressions for this purpose are kept in 'extraFileRedirectExprs' and will be used later.
extraFileRedirectExprs.Add(Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_CallDoCompleteForExpression));
// The 'UnBind' and 'Dispose' operations on 'FileRedirection' objects must be done in the reverse order of 'Bind' operations.
// Namely, it should be done is this order:
// try {
// // The order is A, B
// fileRedirectionA = new FileRedirection(..)
// fileRedirectionA.BindForExpression()
// fileRedirectionB = new FileRedirection(..)
// fileRedirectionB.BindForExpression()
// ...
// } finally {
// // The oder must be B, A
// fileRedirectionB.UnBind()
// fileRedirectionB.Dispose()
// fileRedirectionA.UnBind()
// fileRedirectionA.Dispose()
// }
//
// Otherwise, the original pipe might not be correctly restored in the end. For example,
// '1 *> b.txt > a.txt; 123' would result in the following error when evaluating '123':
// "Cannot perform operation because object "PipelineProcessor" has already been disposed"
finallyExprs.Insert(0, Expression.Call(
redirectionExpr.Cast(typeof(CommandRedirection)),
CachedReflectionInfo.CommandRedirection_UnbindForExpression,
s_functionContext,
savedPipes));
// In either case, we must dispose of the redirection or file handles won't get released.
finallyExprs.Insert(1, Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_Dispose));
}
Expression result = null;
var parenExpr = commandExpr.Expression as ParenExpressionAst;
if (parenExpr != null)
{
// Special processing for paren expressions that capture output.
// Insert any merge redirect expressions during paren expression compilation.
var assignmentStatementAst = parenExpr.Pipeline as AssignmentStatementAst;
if (assignmentStatementAst != null)
{
result = CompileAssignment(
assignmentStatementAst,
(mergeExprs, mergeFinallyExprs) => AddMergeRedirectionExpressions(commandExpr.Redirections, temps, mergeExprs, mergeFinallyExprs));
}
else
{
bool shouldPreserveResultInCaseofException = parenExpr.ShouldPreserveOutputInCaseOfException();
result = CaptureAstResults(
parenExpr.Pipeline,
shouldPreserveResultInCaseofException
? CaptureAstContext.AssignmentWithResultPreservation
: CaptureAstContext.AssignmentWithoutResultPreservation,
(mergeExprs, mergeFinallyExprs) => AddMergeRedirectionExpressions(commandExpr.Redirections, temps, mergeExprs, mergeFinallyExprs));
}
}
else if (subExpr != null)
{
// Include any redirection merging.
AddMergeRedirectionExpressions(commandExpr.Redirections, temps, exprs, finallyExprs);
exprs.Add(Compile(subExpr.SubExpression));
if (resultList != null)
{
// If there is no resultList, we wrote our results of the subexpression directly to the pipe
// instead of being collected to be written here.
result = Expression.Call(CachedReflectionInfo.PipelineOps_PipelineResult, resultList);
}
}
else
{
// Include any redirection merging.
AddMergeRedirectionExpressions(commandExpr.Redirections, temps, exprs, finallyExprs);
result = Compile(commandExpr.Expression);
}
if (result != null)
{
if (!outputRedirected && captureForInput)
{
// If we are capturing the input (for code like: $foo.Bar() 2>&1 | downstream), then we must
// capture the expression results, unless output was redirected to a file because in that case,
// the output can go straight to a file.
exprs.Add(result);
}
else
{
exprs.Add(CallAddPipe(result, s_getCurrentPipe));
// Make sure the result of the expression we return is AutomationNull.Value.
exprs.Add(ExpressionCache.AutomationNullConstant);
}
}
if (extraFileRedirectExprs != null)
{
// Now that the redirection is done, we need to call 'DoComplete' on the file redirection pipeline processors.
// Exception may be thrown when running 'DoComplete', but we don't want the exception to affect the cleanup
// work in 'finallyExprs'. We generate the following code to make sure it does what we want.
// try {
// try {
// exprs
// } finally {
// extraFileRedirectExprs
// }
// finally {
// finallyExprs
// }
var wrapExpr = Expression.TryFinally(Expression.Block(exprs), Expression.Block(extraFileRedirectExprs));
exprs.Clear();
exprs.Add(wrapExpr);
}
if (oldPipe != null)
{
// If a temporary pipe was created at the beginning, we should restore the original pipe in the
// very end of the finally block. Otherwise, s_getCurrentPipe may be messed up by the following
// file redirection unbind operation.
// For example:
// function foo
// {
// [cmdletbinding()]
// param()
// $(gcm NoExist) > test.txt | % { $_ } ## file redirect with new temp pipe
// "hello"
// }
// before this change, running 'foo' will not write out 'hello'.
finallyExprs.Add(Expression.Assign(s_getCurrentPipe, oldPipe));
}
if (finallyExprs.Count != 0)
{
return Expression.Block(temps.ToArray(), Expression.TryFinally(Expression.Block(exprs), Expression.Block(finallyExprs)));
}
return Expression.Block(temps.ToArray(), exprs);
}
private void AddMergeRedirectionExpressions(
ReadOnlyCollection<RedirectionAst> redirections,
List<ParameterExpression> temps,
List<Expression> exprs,
List<Expression> finallyExprs)
{
foreach (var mergingRedirectionAst in redirections.OfType<MergingRedirectionAst>())
{
var savedPipes = NewTemp(typeof(Pipe[]), "savedPipes");
temps.Add(savedPipes);
var redirectionExpr = Expression.Constant(VisitMergingRedirection(mergingRedirectionAst));
exprs.Add(
Expression.Assign(savedPipes,
Expression.Call(
redirectionExpr,
CachedReflectionInfo.MergingRedirection_BindForExpression,
s_executionContextParameter,
s_functionContext)));
// Undo merging redirections first (so file redirections get undone after).
finallyExprs.Insert(0, Expression.Call(
redirectionExpr.Cast(typeof(CommandRedirection)),
CachedReflectionInfo.CommandRedirection_UnbindForExpression,
s_functionContext,
savedPipes));
}
}
public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst)
{
// Most Visit* methods return a Linq.Expression, this method being an exception. VisitPipeline
// may be able to pass a compile time array of redirections if all of the redirections are
// constant. Merging redirections never vary at runtime, so there is no sense in deferring
// the creation of the merging object until runtime.
return new MergingRedirection(mergingRedirectionAst.FromStream, mergingRedirectionAst.ToStream);
}
public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst)
{
Expression fileNameExpr;
var strConst = fileRedirectionAst.Location as StringConstantExpressionAst;
if (strConst != null)
{
// When the filename is a constant, we still must generate a new FileRedirection
// at runtime because we can't keep a cached object in the closure because the object
// is disposed after executing the command.
fileNameExpr = Compile(strConst);
}
else
{
// The filename is not constant, so we must generate code to evaluate the filename at runtime.
fileNameExpr = DynamicExpression.Dynamic(
PSToStringBinder.Get(),
typeof(string),
CompileExpressionOperand(fileRedirectionAst.Location),
s_executionContextParameter);
}
return Expression.New(
CachedReflectionInfo.FileRedirection_ctor,
Expression.Constant(fileRedirectionAst.FromStream),
ExpressionCache.Constant(fileRedirectionAst.Append),
fileNameExpr);
}
public object VisitCommand(CommandAst commandAst)
{
var commandElements = commandAst.CommandElements;
Expression[] elementExprs = new Expression[commandElements.Count];
for (int i = 0; i < commandElements.Count; ++i)
{
var element = commandElements[i];
if (element is CommandParameterAst)
{
elementExprs[i] = Compile(element);
}
else
{
var splatTest = element;
bool splatted = false;
UsingExpressionAst usingExpression = element as UsingExpressionAst;
if (usingExpression != null)
{
splatTest = usingExpression.SubExpression;
}
VariableExpressionAst variableExpression = splatTest as VariableExpressionAst;
if (variableExpression != null)
{
splatted = variableExpression.Splatted;
}
elementExprs[i] = Expression.Call(
CachedReflectionInfo.CommandParameterInternal_CreateArgument,
Expression.Convert(GetCommandArgumentExpression(element), typeof(object)),
Expression.Constant(element),
ExpressionCache.Constant(splatted));
}
}
Expression result = Expression.NewArrayInit(typeof(CommandParameterInternal), elementExprs);
if (commandElements.Count == 2 && commandElements[1] is ParenExpressionAst)
{
// If they used method invocation syntax, we want a strict mode error. We can't
// check that at compile time as it might change at runtime, so we'll add a runtime check.
var args = ((ParenExpressionAst)commandElements[1]).Pipeline.GetPureExpression();
if (args is ArrayLiteralAst)
{
if (commandElements[0].Extent.EndColumnNumber == commandElements[1].Extent.StartColumnNumber
&& commandElements[0].Extent.EndLineNumber == commandElements[1].Extent.StartLineNumber)
{
// no whitespace b/w command name and paren, add a strict mode check
result = Expression.Block(
Expression.IfThen(
Compiler.IsStrictMode(2, s_executionContextParameter),
Compiler.ThrowRuntimeError("StrictModeFunctionCallWithParens", ParserStrings.StrictModeFunctionCallWithParens)),
result);
}
}
}
return result;
}
private Expression GetCommandArgumentExpression(CommandElementAst element)
{
var constElement = element as ConstantExpressionAst;
if (constElement != null
&& (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constElement.StaticType))
|| constElement.StaticType == typeof(System.Numerics.BigInteger)))
{
var commandArgumentText = constElement.Extent.Text;
if (!commandArgumentText.Equals(constElement.Value.ToString(), StringComparison.Ordinal))
{
// If the ToString on the constant would differ from what the user specified, then wrap the
// value so we can recover the actual argument text.
return Expression.Constant(ParserOps.WrappedNumber(constElement.Value, commandArgumentText));
}
}
var result = Compile(element);
if (result.Type == typeof(object[]))
{
result = Expression.Call(CachedReflectionInfo.PipelineOps_CheckAutomationNullInCommandArgumentArray, result);
}
else if (constElement == null && result.Type == typeof(object))
{
result = Expression.Call(CachedReflectionInfo.PipelineOps_CheckAutomationNullInCommandArgument, result);
}
return result;
}
public object VisitCommandExpression(CommandExpressionAst commandExpressionAst)
{
var child = commandExpressionAst.Expression;
var expr = Compile(child);
var unary = child as UnaryExpressionAst;
if ((unary != null && unary.TokenKind.HasTrait(TokenFlags.PrefixOrPostfixOperator)) || expr.Type == typeof(void))
{
return expr;
}
return CallAddPipe(expr, s_getCurrentPipe);
}
public object VisitCommandParameter(CommandParameterAst commandParameterAst)
{
var arg = commandParameterAst.Argument;
var errorPos = commandParameterAst.ErrorPosition;
if (arg != null)
{
bool spaceAfterParameter = errorPos.EndLineNumber != arg.Extent.StartLineNumber ||
errorPos.EndColumnNumber != arg.Extent.StartColumnNumber;
return Expression.Call(
CachedReflectionInfo.CommandParameterInternal_CreateParameterWithArgument,
Expression.Constant(commandParameterAst),
Expression.Constant(commandParameterAst.ParameterName),
Expression.Constant(errorPos.Text),
Expression.Constant(arg),
Expression.Convert(GetCommandArgumentExpression(arg), typeof(object)),
ExpressionCache.Constant(spaceAfterParameter),
ExpressionCache.Constant(false));
}
return Expression.Call(
CachedReflectionInfo.CommandParameterInternal_CreateParameter,
Expression.Constant(commandParameterAst.ParameterName),
Expression.Constant(errorPos.Text),
Expression.Constant(commandParameterAst));
}
internal static Expression ThrowRuntimeError(string errorID, string resourceString, params Expression[] exceptionArgs)
{
return ThrowRuntimeError(errorID, resourceString, typeof(object), exceptionArgs);
}
internal static Expression ThrowRuntimeError(string errorID, string resourceString, Type throwResultType, params Expression[] exceptionArgs)
{
return ThrowRuntimeError(typeof(RuntimeException), errorID, resourceString, throwResultType, exceptionArgs);
}
internal static Expression ThrowRuntimeError(Type exceptionType, string errorID, string resourceString, Type throwResultType, params Expression[] exceptionArgs)
{
var exceptionArgArray = exceptionArgs != null
? Expression.NewArrayInit(typeof(object), exceptionArgs.Select(static e => e.Cast(typeof(object))))
: ExpressionCache.NullConstant;
Expression[] argExprs = new Expression[]
{
ExpressionCache.NullConstant, // targetObject
Expression.Constant(exceptionType, typeof(Type)), // exceptionType
ExpressionCache.NullExtent, // errorPosition
Expression.Constant(errorID), // ErrorID
Expression.Constant(resourceString), // error message
exceptionArgArray // args to use when formatting error message
};
return Expression.Throw(
Expression.Call(CachedReflectionInfo.InterpreterError_NewInterpreterException, argExprs),
throwResultType);
}
internal static Expression ThrowRuntimeErrorWithInnerException(
string errorID,
string resourceString,
Expression innerException,
params Expression[] exceptionArgs)
{
return ThrowRuntimeErrorWithInnerException(errorID, Expression.Constant(resourceString), innerException, typeof(object), exceptionArgs);
}
internal static Expression ThrowRuntimeErrorWithInnerException(
string errorID,
Expression resourceString,
Expression innerException,
Type throwResultType,
params Expression[] exceptionArgs)
{
var exceptionArgArray = exceptionArgs != null
? Expression.NewArrayInit(typeof(object), exceptionArgs)
: ExpressionCache.NullConstant;
Expression[] argExprs = new Expression[]
{
ExpressionCache.NullConstant, // targetObject
Expression.Constant(typeof(RuntimeException), typeof(Type)), // exceptionType
ExpressionCache.NullExtent, // errorPosition
Expression.Constant(errorID), // ErrorID
resourceString, // error message
innerException, // innerException
exceptionArgArray // args to use when formatting error message
};
return Expression.Throw(
Expression.Call(CachedReflectionInfo.InterpreterError_NewInterpreterExceptionWithInnerException, argExprs),
throwResultType);
}
internal static Expression CreateThrow(Type resultType, Type exception, Type[] exceptionArgTypes, params object[] exceptionArgs)
{
Diagnostics.Assert(exceptionArgTypes.Length == exceptionArgs.Length, "types count must match args count for constructor call.");
Expression[] argExprs = new Expression[exceptionArgs.Length];
for (int i = 0; i < exceptionArgs.Length; i++)
{
object o = exceptionArgs[i];
Expression e = Expression.Constant(o, exceptionArgTypes[i]);
argExprs[i] = e;
}
const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
ConstructorInfo constructor = exception.GetConstructor(Flags, null, CallingConventions.Any, exceptionArgTypes, null);
if (constructor == null)
{
throw new PSArgumentException("Type doesn't have constructor with a given signature");
}
return Expression.Throw(Expression.New(constructor, argExprs), resultType);
}
internal static Expression CreateThrow(Type resultType, Type exception, params object[] exceptionArgs)
{
Type[] argTypes = Type.EmptyTypes;
if (exceptionArgs != null)
{
argTypes = new Type[exceptionArgs.Length];
for (int i = 0; i < exceptionArgs.Length; i++)
{
Diagnostics.Assert(exceptionArgs[i] != null, "Can't deduce argument type from null");
argTypes[i] = exceptionArgs[i].GetType();
}
}
return CreateThrow(resultType, exception, argTypes, exceptionArgs);
}
public object VisitSwitchStatement(SwitchStatementAst switchStatementAst)
{
var avs = new AutomaticVarSaver(this, SpecialVariables.UnderbarVarPath, (int)AutomaticVariable.Underbar);
var temps = new List<ParameterExpression>();
ParameterExpression skipDefault = null;
if (switchStatementAst.Default != null)
{
skipDefault = NewTemp(typeof(bool), "skipDefault");
temps.Add(skipDefault);
}
var switchBodyGenerator = GetSwitchBodyGenerator(switchStatementAst, avs, skipDefault);
if ((switchStatementAst.Flags & SwitchFlags.File) != 0)
{
// Generate:
//
// string path = SwitchOps.ResolveFilePath(cond.Extent, cond, context);
// StreamReader sr = null;
// try
// {
// sr = new StreamReader(path);
// string line;
// while ((line = sr.ReadLine()) != null)
// {
// $_ = line
// test clauses
// }
// }
// catch (FlowControlException) { throw; }
// catch (Exception exception)
// {
// CommandProcessorBase.CheckForSevereException(exception);
// // "The file could not be read:" + fne.Message
// throw InterpreterError.NewInterpreterExceptionWithInnerException(path, typeof(RuntimeException),
// cond.Extent, "FileReadError", ParserStrings.FileReadError, exception, exception.Message);
// }
// finally
// {
// if (sr != null) sr.Dispose();
// }
var exprs = new List<Expression>();
var path = NewTemp(typeof(string), "path");
temps.Add(path);
exprs.Add(UpdatePosition(switchStatementAst.Condition));
// We should not preserve the partial output if exception is thrown when evaluating the condition.
var cond = DynamicExpression.Dynamic(
PSToStringBinder.Get(),
typeof(string),
CaptureStatementResults(switchStatementAst.Condition, CaptureAstContext.AssignmentWithoutResultPreservation),
s_executionContextParameter);
exprs.Add(
Expression.Assign(
path,
Expression.Call(
CachedReflectionInfo.SwitchOps_ResolveFilePath,
Expression.Constant(switchStatementAst.Condition.Extent),
cond,
s_executionContextParameter)));
var tryBodyExprs = new List<Expression>();
var streamReader = NewTemp(typeof(StreamReader), "streamReader");
var line = NewTemp(typeof(string), "line");
temps.Add(streamReader);
temps.Add(line);
tryBodyExprs.Add(
Expression.Assign(streamReader, Expression.New(CachedReflectionInfo.StreamReader_ctor, path)));
var loopTest =
Expression.NotEqual(
Expression.Assign(line, Expression.Call(streamReader, CachedReflectionInfo.StreamReader_ReadLine)).Cast(typeof(object)),
ExpressionCache.NullConstant);
tryBodyExprs.Add(avs.SaveAutomaticVar());
tryBodyExprs.Add(
GenerateWhileLoop(
switchStatementAst.Label,
() => loopTest,
(loopBody, breakTarget, continueTarget) => switchBodyGenerator(loopBody, line)));
var tryBlock = Expression.Block(tryBodyExprs);
var finallyBlock = Expression.Block(
Expression.IfThen(
Expression.NotEqual(streamReader, ExpressionCache.NullConstant),
Expression.Call(streamReader.Cast(typeof(IDisposable)), CachedReflectionInfo.IDisposable_Dispose)),
avs.RestoreAutomaticVar());
var exception = NewTemp(typeof(Exception), "exception");
var catchAllBlock = Expression.Block(
tryBlock.Type,
ThrowRuntimeErrorWithInnerException(
"FileReadError",
ParserStrings.FileReadError,
exception,
Expression.Property(exception, CachedReflectionInfo.Exception_Message)));
exprs.Add(Expression.TryCatchFinally(
tryBlock, finallyBlock,
new[]
{
Expression.Catch(typeof(FlowControlException), Expression.Rethrow(tryBlock.Type)),
Expression.Catch(exception, catchAllBlock)
}));
return Expression.Block(temps.Concat(avs.GetTemps()), exprs);
}
// We convert:
// switch ($enumerable) {}
// Into:
// $switch = GetEnumerator $enumerable
// if ($switch == $null)
// {
// $switch = (new object[] { $enumerable }).GetEnumerator()
// }
// REVIEW: should we consider this form of switch a loop for the purposes of deciding
// to compile or not? I have a feeling the loop form is uncommon and compiling isn't worth it.
//
var tryStmt = Expression.TryFinally(
Expression.Block(
avs.SaveAutomaticVar(),
GenerateIteratorStatement(
SpecialVariables.switchVarPath,
() => UpdatePosition(switchStatementAst.Condition),
_switchTupleIndex,
switchStatementAst,
switchBodyGenerator)),
avs.RestoreAutomaticVar());
return Expression.Block(temps.Concat(avs.GetTemps()), tryStmt);
}
private Action<List<Expression>, Expression> GetSwitchBodyGenerator(SwitchStatementAst switchStatementAst, AutomaticVarSaver avs, ParameterExpression skipDefault)
{
return (exprs, newValue) =>
{
var clauseEvalBinder = PSSwitchClauseEvalBinder.Get(switchStatementAst.Flags);
exprs.Add(avs.SetNewValue(newValue));
if (skipDefault != null)
{
exprs.Add(Expression.Assign(skipDefault, ExpressionCache.Constant(false)));
}
IsConstantValueVisitor iscvv = new IsConstantValueVisitor();
ConstantValueVisitor cvv = new ConstantValueVisitor();
int clauseCount = switchStatementAst.Clauses.Count;
for (int i = 0; i < clauseCount; i++)
{
var clause = switchStatementAst.Clauses[i];
Expression test;
object constValue = ((bool)clause.Item1.Accept(iscvv)) ? clause.Item1.Accept(cvv) : null;
if (constValue is ScriptBlock)
{
var call = Expression.Call(Expression.Constant(constValue),
CachedReflectionInfo.ScriptBlock_DoInvokeReturnAsIs,
/*useLocalScope=*/ ExpressionCache.Constant(true),
/*errorHandlingBehavior=*/ Expression.Constant(ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe),
/*dollarUnder=*/ GetLocal((int)AutomaticVariable.Underbar).Convert(typeof(object)),
/*input=*/ ExpressionCache.AutomationNullConstant,
/*scriptThis=*/ ExpressionCache.AutomationNullConstant,
/*args=*/ ExpressionCache.NullObjectArray);
test = DynamicExpression.Dynamic(PSConvertBinder.Get(typeof(bool)), typeof(bool), call);
}
else if (constValue != null)
{
SwitchFlags flags = switchStatementAst.Flags;
Expression conditionExpr = constValue is Regex || constValue is WildcardPattern
? (Expression)Expression.Constant(constValue)
: DynamicExpression.Dynamic(
PSToStringBinder.Get(),
typeof(string),
(constValue is Type)
? Expression.Constant(constValue, typeof(Type))
: Expression.Constant(constValue),
s_executionContextParameter);
Expression currentAsString =
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), GetLocal((int)AutomaticVariable.Underbar),
s_executionContextParameter);
if ((flags & SwitchFlags.Regex) != 0 || constValue is Regex)
{
test = Expression.Call(CachedReflectionInfo.SwitchOps_ConditionSatisfiedRegex,
/*caseSensitive=*/ ExpressionCache.Constant((flags & SwitchFlags.CaseSensitive) != 0),
/*condition=*/ conditionExpr,
/*errorPosition=*/ Expression.Constant(clause.Item1.Extent),
/*str=*/ currentAsString,
/*context=*/ s_executionContextParameter);
}
else if ((flags & SwitchFlags.Wildcard) != 0 || constValue is WildcardPattern)
{
// It would be a little better to just build the wildcard at compile time, but
// the runtime method must exist when variable cases are involved.
test = Expression.Call(CachedReflectionInfo.SwitchOps_ConditionSatisfiedWildcard,
/*caseSensitive=*/ ExpressionCache.Constant((flags & SwitchFlags.CaseSensitive) != 0),
/*condition=*/ conditionExpr,
/*str=*/ currentAsString,
/*context=*/ s_executionContextParameter);
}
else
{
test = CallStringEquals(conditionExpr, currentAsString, ((flags & SwitchFlags.CaseSensitive) == 0));
}
}
else
{
var cond = Compile(clause.Item1);
test = DynamicExpression.Dynamic(
clauseEvalBinder,
typeof(bool),
cond,
GetLocal((int)AutomaticVariable.Underbar),
s_executionContextParameter);
}
exprs.Add(UpdatePosition(clause.Item1));
if (skipDefault != null)
{
exprs.Add(Expression.IfThen(
test,
Expression.Block(Compile(clause.Item2),
Expression.Assign(skipDefault, ExpressionCache.Constant(true)))));
}
else
{
exprs.Add(Expression.IfThen(test, Compile(clause.Item2)));
}
}
if (skipDefault != null)
{
exprs.Add(Expression.IfThen(Expression.Not(skipDefault), Compile(switchStatementAst.Default)));
}
};
}
public object VisitDataStatement(DataStatementAst dataStatementAst)
{
// We'll generate the following:
//
// try
// {
// oldLanguageMode = context.LanguageMode;
// CheckLanguageMode (if necessary, only if SupportedCommands specified)
// CheckAllowedCommands (if necessary, it may have been done at parse time if possible)
// context.LanguageMode = PSLanguageMode.RestrictedLanguage;
// either:
// dataStatementAst.Variable = execute body
// or if the variable name was not specified:
// execute body, writing results to the current output pipe
// }
// finally
// {
// context.LanguageMode = oldLanguageMode;
// }
var oldLanguageMode = NewTemp(typeof(PSLanguageMode), "oldLanguageMode");
var languageModePropertyExpr = Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_LanguageMode);
var exprs = new List<Expression>
{
// Save old language mode before doing anything else so if there is an exception, we don't
// "restore" the language mode to an uninitialized value (which happens to be "full" mode.
Expression.Assign(oldLanguageMode, languageModePropertyExpr),
UpdatePosition(dataStatementAst),
// Auto-import utility module if needed, so that ConvertFrom-StringData isn't set to RestrictedLanguage
// by the data section.
Expression.Call(
CachedReflectionInfo.RestrictedLanguageChecker_EnsureUtilityModuleLoaded,
s_executionContextParameter)
};
if (dataStatementAst.CommandsAllowed.Count > 0)
{
// If CommandsAllowed was specified, we need to check the language mode - the data section runs
// in restricted language mode and we don't want to allow disallowed commands to run if we were in
// constrained language mode.
exprs.Add(
Expression.Call(
CachedReflectionInfo.RestrictedLanguageChecker_CheckDataStatementLanguageModeAtRuntime,
Expression.Constant(dataStatementAst),
s_executionContextParameter));
}
if (dataStatementAst.HasNonConstantAllowedCommand)
{
// We couldn't check the commands allowed at parse time, so we must do so at runtime
exprs.Add(
Expression.Call(CachedReflectionInfo.RestrictedLanguageChecker_CheckDataStatementAstAtRuntime,
Expression.Constant(dataStatementAst),
Expression.NewArrayInit(typeof(string),
dataStatementAst.CommandsAllowed.Select(elem => Compile(elem).Convert(typeof(string))))));
}
exprs.Add(Expression.Assign(languageModePropertyExpr, Expression.Constant(PSLanguageMode.RestrictedLanguage)));
// If we'll be assigning directly, we need to capture the value, otherwise just write to the current output pipe.
// We should not preserve the partial output if exception is thrown when evaluating the body expr.
var dataExpr = dataStatementAst.Variable != null
? CaptureAstResults(dataStatementAst.Body, CaptureAstContext.AssignmentWithoutResultPreservation).Cast(typeof(object))
: Compile(dataStatementAst.Body);
exprs.Add(dataExpr);
var block = Expression.Block(
new[] { oldLanguageMode },
Expression.TryFinally(Expression.Block(exprs), Expression.Assign(languageModePropertyExpr, oldLanguageMode)));
if (dataStatementAst.Variable != null)
{
return dataStatementAst.TupleIndex < 0
? CallSetVariable(Expression.Constant(new VariablePath("local:" + dataStatementAst.Variable)), block)
: Expression.Assign(GetLocal(dataStatementAst.TupleIndex), block);
}
return block;
}
private static CatchBlock[] GenerateLoopBreakContinueCatchBlocks(string label, LabelTarget breakLabel, LabelTarget continueLabel)
{
var breakExceptionVar = Expression.Parameter(typeof(BreakException));
var continueExceptionVar = Expression.Parameter(typeof(ContinueException));
return new[]
{
// catch (BreakException ev) { if (ev.MatchLabel(loopStatement.Label) { goto breakTarget; } throw; }
Expression.Catch(
breakExceptionVar,
Expression.IfThenElse(
Expression.Call(breakExceptionVar,
CachedReflectionInfo.LoopFlowException_MatchLabel,
Expression.Constant(label ?? string.Empty, typeof(string))),
Expression.Break(breakLabel),
Expression.Rethrow())),
// catch (ContinueException ev) { if (ev.MatchLabel(loopStatement.Label) { goto continueTarget; } throw; }
Expression.Catch(
continueExceptionVar,
Expression.IfThenElse(
Expression.Call(
continueExceptionVar,
CachedReflectionInfo.LoopFlowException_MatchLabel,
Expression.Constant(label ?? string.Empty, typeof(string))),
Expression.Continue(continueLabel),
Expression.Rethrow()))
};
}
private Expression GenerateWhileLoop(string loopLabel,
Func<Expression> generateCondition,
Action<List<Expression>, LabelTarget, LabelTarget> generateLoopBody,
PipelineBaseAst continueAst = null)
{
// If continueAst is not null, generate:
// LoopTop:
// if (condition)
// {
// try {
// loop body
// // break -> goto BreakTarget
// // continue -> goto ContinueTarget
// } catch (BreakException be) {
// if (be.MatchLabel(loopLabel)) goto BreakTarget;
// throw;
// } catch (ContinueException ce) {
// if (ce.MatchLabel(loopLabel)) goto ContinueTarget;
// throw;
// }
// ContinueTarget:
// continueAction
// goto LoopTop:
// }
// :BreakTarget
//
// If continueAst is null, generate:
// ContinueTarget:
// if (condition)
// {
// try {
// loop body
// // break -> goto BreakTarget
// // continue -> goto ContinueTarget
// goto ContinueTarget
// } catch (BreakException be) {
// if (be.MatchLabel(loopLabel)) goto BreakTarget;
// throw;
// } catch (ContinueException ce) {
// if (ce.MatchLabel(loopLabel)) goto ContinueTarget;
// throw;
// }
// }
// :BreakTarget
int preStmtCount = _stmtCount;
var exprs = new List<Expression>();
var continueLabel = Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel + "Continue" : "continue");
var breakLabel = Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel + "Break" : "break");
var enterLoop = new EnterLoopExpression();
var loopTop = (continueAst != null)
? Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel + "LoopTop" : "looptop")
: continueLabel;
exprs.Add(Expression.Label(loopTop));
exprs.Add(enterLoop);
var loopBodyExprs = new List<Expression>();
loopBodyExprs.Add(s_callCheckForInterrupts);
_loopTargets.Add(new LoopGotoTargets(loopLabel ?? string.Empty, breakLabel, continueLabel));
_generatingWhileOrDoLoop = true;
generateLoopBody(loopBodyExprs, breakLabel, continueLabel);
_generatingWhileOrDoLoop = false;
if (continueAst == null)
{
loopBodyExprs.Add(Expression.Goto(loopTop));
}
_loopTargets.RemoveAt(_loopTargets.Count - 1);
Expression loopBody =
Expression.TryCatch(Expression.Block(loopBodyExprs), GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel));
if (continueAst != null)
{
var x = new List<Expression>();
x.Add(loopBody);
x.Add(Expression.Label(continueLabel));
if (continueAst.GetPureExpression() != null)
{
// Assignments generate the code to update the position automatically,
// but pre/post increments don't, so we add it here explicitly.
x.Add(UpdatePosition(continueAst));
}
// We should not preserve the partial output if exception is thrown when evaluating the continueAst.
x.Add(CaptureStatementResults(continueAst, CaptureAstContext.AssignmentWithoutResultPreservation));
x.Add(Expression.Goto(loopTop));
loopBody = Expression.Block(x);
}
if (generateCondition != null)
{
exprs.Add(Expression.IfThen(generateCondition().Convert(typeof(bool)), loopBody));
}
else
{
exprs.Add(loopBody);
}
exprs.Add(Expression.Label(breakLabel));
enterLoop.LoopStatementCount = _stmtCount - preStmtCount;
return enterLoop.Loop = new PowerShellLoopExpression(exprs);
}
private Expression GenerateDoLoop(LoopStatementAst loopStatement)
{
// Generate code like:
// :RepeatTarget
// try {
// loop body
// // break -> goto BreakTarget
// // continue -> goto TestBlock
// } catch (BreakException be) {
// if (be.MatchLabel(loopLabel))
// goto BreakTarget;
// throw;
// } catch (ContinueException ce) {
// if (ce.MatchLabel(loopLabel))
// goto ContinueTarget;
// throw;
// }
// :ContinueTarget
// if (condition)
// {
// goto RepeatTarget
// }
// :BreakTarget
//
int preStmtCount = _stmtCount;
string loopLabel = loopStatement.Label;
var exprs = new List<Expression>();
var repeatLabel = Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel : null);
var continueLabel = Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel + "Continue" : "continue");
var breakLabel = Expression.Label(!string.IsNullOrEmpty(loopLabel) ? loopLabel + "Break" : "break");
var enterLoopExpression = new EnterLoopExpression();
exprs.Add(Expression.Label(repeatLabel));
exprs.Add(enterLoopExpression);
_loopTargets.Add(new LoopGotoTargets(loopLabel ?? string.Empty, breakLabel, continueLabel));
_generatingWhileOrDoLoop = true;
var loopBodyExprs = new List<Expression>
{
s_callCheckForInterrupts,
Compile(loopStatement.Body),
ExpressionCache.Empty
};
_generatingWhileOrDoLoop = false;
_loopTargets.RemoveAt(_loopTargets.Count - 1);
exprs.Add(Expression.TryCatch(
Expression.Block(loopBodyExprs),
GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel)));
exprs.Add(Expression.Label(continueLabel));
var test = CaptureStatementResults(loopStatement.Condition, CaptureAstContext.Condition).Convert(typeof(bool));
if (loopStatement is DoUntilStatementAst)
{
test = Expression.Not(test);
}
test = UpdatePositionForInitializerOrCondition(loopStatement.Condition, test);
exprs.Add(Expression.IfThen(test, Expression.Goto(repeatLabel)));
exprs.Add(Expression.Label(breakLabel));
enterLoopExpression.LoopStatementCount = _stmtCount - preStmtCount;
return enterLoopExpression.Loop = new PowerShellLoopExpression(exprs);
}
private Expression GenerateIteratorStatement(
VariablePath iteratorVariablePath,
Func<Expression> generateMoveNextUpdatePosition,
int iteratorTupleIndex,
LabeledStatementAst stmt,
Action<List<Expression>, Expression> generateBody)
{
// We convert:
// foreach ($x in $enumerable) {}
// Into:
// try
// {
// $oldforeach = $foreach
// $enumerable = condition
// $foreach = GetEnumerator $enumerable
// if ($foreach == $null && $enumerable != $null)
// {
// $foreach = (new object[] { $enumerable }).GetEnumerator()
// }
// if ($foreach != $null)
// {
// while ($foreach.MoveNext())
// {
// $x = $foreach.Current
// }
// }
// }
// finally
// {
// $foreach = $oldforeach
// }
// The translation for switch is similar.
//
var temps = new List<ParameterExpression>();
var exprs = new List<Expression>();
var avs = new AutomaticVarSaver(this, iteratorVariablePath, iteratorTupleIndex);
bool generatingForeach = stmt is ForEachStatementAst;
exprs.Add(avs.SaveAutomaticVar());
// $enumerable = condition
// $foreach/$switch = GetEnumerator $enumerable
var enumerable = NewTemp(typeof(object), "enumerable");
temps.Add(enumerable);
// Update position to make it safe to access 'CurrentPosition' property in FunctionContext in case
// that the evaluation of 'stmt.Condition' throws exception.
if (generatingForeach)
{
// For foreach statement, we want the debugger to stop at 'stmt.Condition' before evaluating it.
// The debugger will stop at 'stmt.Condition' only once. The following enumeration will stop at
// the foreach variable.
exprs.Add(UpdatePosition(stmt.Condition));
}
else
{
// For switch statement, we don't want the debugger to stop at 'stmt.Condition' before evaluating it.
// The following enumeration will stop at 'stmt.Condition' again, and we don't want the debugger to
// stop at 'stmt.Condition' twice before getting into one of its case clauses.
var extent = stmt.Condition.Extent;
int index = AddSequencePoint(extent);
exprs.Add(new UpdatePositionExpr(extent, index, _debugSymbolDocument, checkBreakpoints: false));
}
exprs.Add(
Expression.Assign(
enumerable,
GetRangeEnumerator(stmt.Condition.GetPureExpression()) ?? CaptureStatementResults(stmt.Condition, CaptureAstContext.Enumerable).Convert(typeof(object))));
var iteratorTemp = NewTemp(typeof(IEnumerator), iteratorVariablePath.UnqualifiedPath);
temps.Add(iteratorTemp);
exprs.Add(
Expression.Assign(
iteratorTemp,
DynamicExpression.Dynamic(PSEnumerableBinder.Get(), typeof(IEnumerator), enumerable)));
// In a foreach, generate:
// if ($foreach == $null && $enumerable != $null)
// {
// $foreach = (new object[] { $enumerable }).GetEnumerator()
// }
// In a switch, generate:
// if ($switch == $null)
// {
// $switch = (new object[] { $enumerable }).GetEnumerator()
// }
var testNeedScalarToEnumerable =
generatingForeach
? Expression.AndAlso(
Expression.Equal(iteratorTemp, ExpressionCache.NullConstant),
Expression.NotEqual(enumerable, ExpressionCache.NullConstant))
: Expression.Equal(iteratorTemp, ExpressionCache.NullConstant);
var scalarToEnumerable =
Expression.Assign(iteratorTemp,
Expression.Call(Expression.NewArrayInit(typeof(object),
Expression.Convert(enumerable, typeof(object))),
CachedReflectionInfo.IEnumerable_GetEnumerator));
exprs.Add(Expression.IfThen(testNeedScalarToEnumerable, scalarToEnumerable));
exprs.Add(avs.SetNewValue(iteratorTemp));
var moveNext = Expression.Block(
generateMoveNextUpdatePosition(),
Expression.Call(iteratorTemp, CachedReflectionInfo.IEnumerator_MoveNext));
var loop = GenerateWhileLoop(
stmt.Label,
() => moveNext,
(loopBody, breakTarget, continueTarget) => generateBody(loopBody, Expression.Property(iteratorTemp, CachedReflectionInfo.IEnumerator_Current)));
// With a foreach, the enumerator may never get assigned, in which case we skip the loop entirely.
// Generate that test.
// With a switch, the switch body is never skipped, so skip generating that test, and skip creating
// target block.
if (generatingForeach)
{
exprs.Add(Expression.IfThen(Expression.NotEqual(iteratorTemp, ExpressionCache.NullConstant), loop));
}
else
{
exprs.Add(loop);
}
return Expression.Block(
temps.Concat(avs.GetTemps()),
Expression.TryFinally(Expression.Block(exprs), avs.RestoreAutomaticVar()));
}
public object VisitForEachStatement(ForEachStatementAst forEachStatementAst)
{
// We convert:
// foreach ($x in $enumerable) {}
// Into:
// $foreach = GetEnumerator $enumerable
// if ($foreach == $null && $enumerable != $null)
// {
// $foreach = (new object[] { $enumerable }).GetEnumerator()
// }
// if ($foreach != $null)
// {
// while ($foreach.MoveNext())
// {
// $x = $foreach.Current
// }
// }
Action<List<Expression>, Expression> loopBodyGenerator =
(exprs, newValue) =>
{
exprs.Add(ReduceAssignment(forEachStatementAst.Variable, TokenKind.Equals, newValue));
exprs.Add(Compile(forEachStatementAst.Body));
};
return GenerateIteratorStatement(
SpecialVariables.foreachVarPath,
() => UpdatePosition(forEachStatementAst.Variable),
_foreachTupleIndex,
forEachStatementAst,
loopBodyGenerator);
}
private Expression GetRangeEnumerator(ExpressionAst condExpr)
{
if (condExpr != null)
{
var binaryExpr = condExpr as BinaryExpressionAst;
if (binaryExpr != null && binaryExpr.Operator == TokenKind.DotDot)
{
Expression lhs = Compile(binaryExpr.Left);
Expression rhs = Compile(binaryExpr.Right);
return Expression.Call(
CachedReflectionInfo.ParserOps_GetRangeEnumerator,
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)));
}
}
return null;
}
private Expression UpdatePositionForInitializerOrCondition(PipelineBaseAst pipelineBaseAst, Expression initializerOrCondition)
{
if (pipelineBaseAst is PipelineAst pipelineAst && !pipelineAst.Background && pipelineAst.GetPureExpression() != null)
{
// If the initializer or condition is a pure expression (CommandExpressionAst without redirection),
// then we need to add a sequence point. If it's an AssignmentStatementAst, we don't need to add
// sequence point here because one will be added when processing the AssignmentStatementAst.
initializerOrCondition = Expression.Block(UpdatePosition(pipelineBaseAst), initializerOrCondition);
}
return initializerOrCondition;
}
public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst)
{
return GenerateDoLoop(doWhileStatementAst);
}
public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst)
{
return GenerateDoLoop(doUntilStatementAst);
}
public object VisitForStatement(ForStatementAst forStatementAst)
{
// We should not preserve the partial output if exception is thrown when evaluating the initializer.
Expression init = null;
PipelineBaseAst initializer = forStatementAst.Initializer;
if (initializer != null)
{
init = CaptureStatementResults(initializer, CaptureAstContext.AssignmentWithoutResultPreservation);
init = UpdatePositionForInitializerOrCondition(initializer, init);
}
PipelineBaseAst condition = forStatementAst.Condition;
var generateCondition = condition != null
? () => UpdatePositionForInitializerOrCondition(condition, CaptureStatementResults(condition, CaptureAstContext.Condition))
: (Func<Expression>)null;
var loop = GenerateWhileLoop(
forStatementAst.Label,
generateCondition,
(loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(forStatementAst.Body)),
forStatementAst.Iterator);
if (init != null)
{
return Expression.Block(init, loop);
}
return loop;
}
public object VisitWhileStatement(WhileStatementAst whileStatementAst)
{
PipelineBaseAst condition = whileStatementAst.Condition;
return GenerateWhileLoop(
whileStatementAst.Label,
() => UpdatePositionForInitializerOrCondition(condition, CaptureStatementResults(condition, CaptureAstContext.Condition)),
(loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(whileStatementAst.Body)));
}
public object VisitCatchClause(CatchClauseAst catchClauseAst)
{
Diagnostics.Assert(false, "the catch body is visited directly from VisitTryStatement.");
return null;
}
// This helper class is used to help save and restore the value of an automatic variable that is set
// as part of executing some statement, then restored to it's previous value after the statement completes.
//
// The basic idea is, if the automatic var has a value in the current frame, save it and restore it.
// If the automatic var has no value in the current frame, then we set the variable's value to $null
// after leaving the stmt.
//
// The psuedo-code:
//
// try {
// oldValue = (localSet.Get(automaticVar)) ? locals.ItemNNN : null;
// locals.ItemNNN = newValue
// localsSet.Set(automaticVar, true);
// any code
// } finally {
// locals.ItemNNN = oldValue;
// }
//
// This is a little convoluted because an automatic variable isn't necessarily set.
private sealed class AutomaticVarSaver
{
private readonly Compiler _compiler;
private readonly int _automaticVar;
private readonly VariablePath _autoVarPath;
private ParameterExpression _oldValue;
internal AutomaticVarSaver(Compiler compiler, VariablePath autoVarPath, int automaticVar)
{
_compiler = compiler;
_autoVarPath = autoVarPath;
_automaticVar = automaticVar;
}
internal IEnumerable<ParameterExpression> GetTemps()
{
Diagnostics.Assert(_oldValue != null, "caller to only call GetTemps after calling SaveAutomaticVar");
yield return _oldValue;
}
internal Expression SaveAutomaticVar()
{
var getValueExpr = _automaticVar < 0
? Compiler.CallGetVariable(Expression.Constant(_autoVarPath), null)
: _compiler.GetLocal(_automaticVar);
_oldValue = _compiler.NewTemp(getValueExpr.Type, "old_" + _autoVarPath.UnqualifiedPath);
return Expression.Assign(_oldValue, getValueExpr);
}
internal Expression SetNewValue(Expression newValue)
{
if (_automaticVar < 0)
{
return Compiler.CallSetVariable(Expression.Constant(_autoVarPath), newValue);
}
return Expression.Assign(_compiler.GetLocal(_automaticVar), newValue);
}
internal Expression RestoreAutomaticVar()
{
if (_automaticVar < 0)
{
return Compiler.CallSetVariable(Expression.Constant(_autoVarPath), _oldValue);
}
return Expression.Assign(_compiler.GetLocal(_automaticVar), _oldValue);
}
}
public object VisitTryStatement(TryStatementAst tryStatementAst)
{
var temps = new List<ParameterExpression>();
var tryBlockExprs = new List<Expression>();
var finallyBlockExprs = new List<Expression>();
// We must set $ExecutionContext.PropagateExceptionsToEnclosingStatementBlock = $true so we don't prompt
// if an exception is raised, and we must restore the previous value when leaving because we can't
// know if we're dynamically executing code guarded by a try/catch.
var oldActiveHandler = NewTemp(typeof(bool), "oldActiveHandler");
temps.Add(oldActiveHandler);
var handlerInScope = Expression.Property(
s_executionContextParameter,
CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock);
tryBlockExprs.Add(Expression.Assign(oldActiveHandler, handlerInScope));
tryBlockExprs.Add(Expression.Assign(handlerInScope, ExpressionCache.Constant(true)));
finallyBlockExprs.Add(Expression.Assign(handlerInScope, oldActiveHandler));
CompileStatementListWithTraps(tryStatementAst.Body.Statements, tryStatementAst.Body.Traps, tryBlockExprs, temps);
var catches = new List<CatchBlock>();
if (tryStatementAst.CatchClauses.Count == 1 && tryStatementAst.CatchClauses[0].IsCatchAll)
{
// Generate:
// catch (RuntimeException rte)
// {
// oldrte = context.CurrentExceptionBeingHandled
// context.CurrentExceptionBeingHandled = rte
// oldDollarUnder = $_
// $_ = new ErrorRecord(rte.ErrorRecord, rte)
// try {
// user catch code
// } finally {
// $_ = oldDollarUnder
// context.CurrentExceptionBeingHandled = oldrte
// }
AutomaticVarSaver avs = new AutomaticVarSaver(
this, SpecialVariables.UnderbarVarPath,
(int)AutomaticVariable.Underbar);
var rte = NewTemp(typeof(RuntimeException), "rte");
var oldrte = NewTemp(typeof(RuntimeException), "oldrte");
var errorRecord = Expression.New(
CachedReflectionInfo.ErrorRecord__ctor,
Expression.Property(rte, CachedReflectionInfo.RuntimeException_ErrorRecord),
rte);
var catchExprs = new List<Expression>
{
Expression.Assign(oldrte, s_currentExceptionBeingHandled),
Expression.Assign(s_currentExceptionBeingHandled, rte),
avs.SaveAutomaticVar(),
avs.SetNewValue(errorRecord)
};
StatementBlockAst statementBlock = tryStatementAst.CatchClauses[0].Body;
CompileStatementListWithTraps(statementBlock.Statements, statementBlock.Traps, catchExprs, temps);
var tf = Expression.TryFinally(
Expression.Block(typeof(void), catchExprs),
Expression.Block(typeof(void), avs.RestoreAutomaticVar(), Expression.Assign(s_currentExceptionBeingHandled, oldrte)));
catches.Add(Expression.Catch(typeof(PipelineStoppedException), Expression.Rethrow()));
catches.Add(Expression.Catch(rte, Expression.Block(avs.GetTemps().Append(oldrte).ToArray(), tf)));
}
else if (tryStatementAst.CatchClauses.Count > 0)
{
// We can't generate a try/catch in quite the way one might expect for a few reasons:
// * At compile time, we may not have loaded the types that are being caught
// * We wrap exceptions in a RuntimeException so they can carry a position.
//
// Instead, we generate something like:
// try {}
// catch (RuntimeException re) {
// try
// {
// oldexception = context.CurrentExceptionBeingHandled
// context.CurrentExceptionBeingHandled = re
// old_ = $_
// $_ = re.ErrorRecord
// switch (ExceptionHandlingOps.FindMatchingHandler(re, types))
// {
// case 0:
// /* first handler */
// break;
// case 1:
// case 2:
// /* second handler (we do allow a single handler for multiple types) */
// break;
// default:
// /* no matching handler, but could be a trap or user might want prompting */
// /* will rethrow the exception if that's what we need to do */
// ExceptionHandlingOps.CheckActionPreference(functionContext, exception);
// }
// } finally {
// $_ = old_
// context.CurrentExceptionBeingHandled = oldexception
// }
// }
// Try to get the types at compile time. We could end up with nulls in this array, we'll handle
// that with runtime code.
int countTypes = 0;
for (int index = 0; index < tryStatementAst.CatchClauses.Count; index++)
{
var c = tryStatementAst.CatchClauses[index];
// If CatchTypes.Count is empty, we still want to count the catch all handler.
countTypes += Math.Max(c.CatchTypes.Count, 1);
}
var catchTypes = new Type[countTypes];
Expression catchTypesExpr = Expression.Constant(catchTypes);
var dynamicCatchTypes = new List<Expression>();
var cases = new List<SwitchCase>();
int handlerTypeIndex = 0;
int i = 0;
var exception = Expression.Parameter(typeof(RuntimeException));
for (int index = 0; index < tryStatementAst.CatchClauses.Count; index++)
{
var c = tryStatementAst.CatchClauses[index];
if (c.IsCatchAll)
{
catchTypes[i] = typeof(ExceptionHandlingOps.CatchAll);
}
else
{
for (int index1 = 0; index1 < c.CatchTypes.Count; index1++)
{
var ct = c.CatchTypes[index1];
catchTypes[i] = ct.TypeName.GetReflectionType();
if (catchTypes[i] == null)
{
// Type needs to be resolved at runtime, so we'll use code like:
//
// if (catchTypes[i] == null) catchTypes[i] = ResolveTypeName(ct.TypeName)
//
// We use a constant array, resolve just once (unless it fails) to prevent re-resolving
// each time it executes.
var indexExpr = Expression.ArrayAccess(catchTypesExpr, ExpressionCache.Constant(i));
dynamicCatchTypes.Add(
Expression.IfThen(
Expression.Equal(indexExpr, ExpressionCache.NullType),
Expression.Assign(
indexExpr,
Expression.Call(
CachedReflectionInfo.TypeOps_ResolveTypeName,
Expression.Constant(ct.TypeName),
Expression.Constant(ct.Extent)))));
}
i += 1;
}
}
// Wrap the body in a void block so all cases have the same type.
var catchBody = Expression.Block(typeof(void), Compile(c.Body));
if (c.IsCatchAll)
{
cases.Add(Expression.SwitchCase(catchBody, ExpressionCache.Constant(handlerTypeIndex)));
handlerTypeIndex += 1;
}
else
{
cases.Add(Expression.SwitchCase(catchBody,
Enumerable.Range(handlerTypeIndex, c.CatchTypes.Count).Select(ExpressionCache.Constant)));
handlerTypeIndex += c.CatchTypes.Count;
}
}
if (dynamicCatchTypes.Count > 0)
{
// This might be worth a strict-mode check - if there was a typo, the typo isn't discovered until
// the first time an exception is raised, which is rather unfortunate.
catchTypesExpr = Expression.Block(dynamicCatchTypes.Append(catchTypesExpr));
}
AutomaticVarSaver avs = new AutomaticVarSaver(this, SpecialVariables.UnderbarVarPath, (int)AutomaticVariable.Underbar);
var swCond = Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_FindMatchingHandler,
LocalVariablesParameter,
exception,
catchTypesExpr,
s_executionContextParameter);
var oldexception = NewTemp(typeof(RuntimeException), "oldrte");
var tf = Expression.TryFinally(
Expression.Block(
typeof(void),
Expression.Assign(oldexception, s_currentExceptionBeingHandled),
Expression.Assign(s_currentExceptionBeingHandled, exception),
avs.SaveAutomaticVar(),
// $_ is set in the call to ExceptionHandlingOps.FindMatchingHandler
Expression.Switch(
swCond,
Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference,
s_functionContext, exception),
cases.ToArray())),
Expression.Block(
avs.RestoreAutomaticVar(),
Expression.Assign(s_currentExceptionBeingHandled, oldexception)));
catches.Add(Expression.Catch(typeof(PipelineStoppedException), Expression.Rethrow()));
catches.Add(Expression.Catch(exception, Expression.Block(avs.GetTemps().Append(oldexception).ToArray(), tf)));
}
if (tryStatementAst.Finally != null)
{
// Generate:
// oldIsStopping = ExceptionHandlingOps.SuspendStoppingPipeline(executionContext);
// try {
// user finally statements
// } finally {
// ExceptionHandlingOps.RestoreStoppingPipeline(executionContext, oldIsStopping);
// }
var oldIsStopping = NewTemp(typeof(bool), "oldIsStopping");
temps.Add(oldIsStopping);
finallyBlockExprs.Add(
Expression.Assign(
oldIsStopping,
Expression.Call(
CachedReflectionInfo.ExceptionHandlingOps_SuspendStoppingPipeline,
s_executionContextParameter)));
var nestedFinallyExprs = new List<Expression>();
CompileStatementListWithTraps(
tryStatementAst.Finally.Statements,
tryStatementAst.Finally.Traps,
nestedFinallyExprs,
temps);
if (nestedFinallyExprs.Count == 0)
{
nestedFinallyExprs.Add(ExpressionCache.Empty);
}
finallyBlockExprs.Add(Expression.Block(
Expression.TryFinally(
Expression.Block(nestedFinallyExprs),
Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_RestoreStoppingPipeline, s_executionContextParameter, oldIsStopping))));
}
// Our result must have void type, so make sure it does.
if (tryBlockExprs[tryBlockExprs.Count - 1].Type != typeof(void))
{
tryBlockExprs.Add(ExpressionCache.Empty);
}
if (catches.Count > 0)
{
return Expression.Block(
temps.ToArray(),
Expression.TryCatchFinally(
Expression.Block(tryBlockExprs),
Expression.Block(finallyBlockExprs),
catches.ToArray()));
}
return Expression.Block(
temps.ToArray(),
Expression.TryFinally(
Expression.Block(tryBlockExprs),
Expression.Block(finallyBlockExprs)));
}
private Expression GenerateBreakOrContinue(
Ast ast,
ExpressionAst label,
Func<LoopGotoTargets, LabelTarget> fieldSelector,
Func<LabelTarget, Expression> exprGenerator,
ConstructorInfo nonLocalExceptionCtor)
{
LabelTarget labelTarget = null;
Expression labelExpr = null;
if (label != null)
{
labelExpr = Compile(label);
if (_loopTargets.Count > 0)
{
var labelStrAst = label as StringConstantExpressionAst;
if (labelStrAst != null)
{
labelTarget = (from t in _loopTargets
where t.Label.Equals(labelStrAst.Value, StringComparison.OrdinalIgnoreCase)
select fieldSelector(t)).LastOrDefault();
}
}
}
else if (_loopTargets.Count > 0)
{
labelTarget = fieldSelector(_loopTargets[_loopTargets.Count - 1]);
}
Expression result;
if (labelTarget != null)
{
result = exprGenerator(labelTarget);
}
else
{
labelExpr ??= ExpressionCache.ConstEmptyString;
result = Expression.Throw(Expression.New(nonLocalExceptionCtor, labelExpr.Convert(typeof(string))));
}
return Expression.Block(UpdatePosition(ast), result);
}
public object VisitBreakStatement(BreakStatementAst breakStatementAst)
{
return GenerateBreakOrContinue(
breakStatementAst,
breakStatementAst.Label,
lgt => lgt.BreakLabel,
Expression.Break,
CachedReflectionInfo.BreakException_ctor);
}
public object VisitContinueStatement(ContinueStatementAst continueStatementAst)
{
return GenerateBreakOrContinue(
continueStatementAst,
continueStatementAst.Label,
lgt => lgt.ContinueLabel,
Expression.Continue,
CachedReflectionInfo.ContinueException_ctor);
}
public object VisitReturnStatement(ReturnStatementAst returnStatementAst)
{
// If we're returning from a trap, we must raise an exception because the trap is a distinct method, but we want
// to return from the function containing the trap, not just the trap itself.
Expression returnExpr;
if (_compilingTrap)
{
returnExpr = Expression.Throw(Expression.New(CachedReflectionInfo.ReturnException_ctor, ExpressionCache.AutomationNullConstant));
}
else
{
returnExpr = Expression.Return(_returnTarget,
_returnTarget.Type == typeof(object)
? ExpressionCache.AutomationNullConstant
: ExpressionCache.Empty);
}
if (returnStatementAst.Pipeline != null)
{
var pipe = returnStatementAst.Pipeline;
var assignmentStatementAst = pipe as AssignmentStatementAst;
Expression returnValue;
if (CompilingMemberFunction)
{
// We used a null pipe for the function body, but for the return statement,
// we need to write to the pipe passed to our dynamic method so InvokeAsMemberFunction
// can get the return value to return it.
returnValue = CaptureStatementResults(returnStatementAst.Pipeline, CaptureAstContext.AssignmentWithoutResultPreservation);
if (MemberFunctionReturnType != typeof(void))
{
// Write directly to the pipe - don't use the dynamic site (CallAddPipe) as that could enumerate.
returnValue = Expression.Call(
s_returnPipe,
CachedReflectionInfo.Pipe_Add,
returnValue.Convert(MemberFunctionReturnType).Cast(typeof(object)));
}
return Expression.Block(UpdatePosition(returnStatementAst.Pipeline),
Expression.Assign(s_getCurrentPipe, s_returnPipe),
returnValue,
returnExpr);
}
returnValue = assignmentStatementAst != null
? CallAddPipe(CompileAssignment(assignmentStatementAst), s_getCurrentPipe)
: Compile(pipe);
return Expression.Block(returnValue, returnExpr);
}
return returnExpr;
}
public object VisitExitStatement(ExitStatementAst exitStatementAst)
{
// We should not preserve the partial output if exception is thrown when evaluating exitStmt.pipeline.
Expression exitCode = exitStatementAst.Pipeline != null
? CaptureStatementResults(exitStatementAst.Pipeline, CaptureAstContext.AssignmentWithoutResultPreservation)
: ExpressionCache.Constant(0);
return Expression.Block(
UpdatePosition(exitStatementAst),
Expression.Throw(Expression.Call(CachedReflectionInfo.PipelineOps_GetExitException,
exitCode.Convert(typeof(object))),
typeof(void)));
}
public object VisitThrowStatement(ThrowStatementAst throwStatementAst)
{
// We should not preserve the partial output if exception is thrown when evaluating throwStmt.pipeline.
Expression throwExpr = throwStatementAst.IsRethrow
? s_currentExceptionBeingHandled
: (throwStatementAst.Pipeline == null)
? ExpressionCache.NullConstant
: CaptureStatementResults(throwStatementAst.Pipeline,
CaptureAstContext.AssignmentWithoutResultPreservation);
return Expression.Block(
UpdatePosition(throwStatementAst),
Expression.Throw(Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToException,
throwExpr.Convert(typeof(object)),
Expression.Constant(throwStatementAst.Extent),
Expression.Constant(throwStatementAst.IsRethrow))));
}
#endregion Statements
#region Expressions
public Expression GenerateCallContains(Expression lhs, Expression rhs, bool ignoreCase)
{
return Expression.Call(
CachedReflectionInfo.ParserOps_ContainsOperatorCompiled,
s_executionContextParameter,
Expression.Constant(CallSite<Func<CallSite, object, IEnumerator>>.Create(PSEnumerableBinder.Get())),
Expression.Constant(CallSite<Func<CallSite, object, object, object>>.Create(
PSBinaryOperationBinder.Get(ExpressionType.Equal, ignoreCase, scalarCompare: true))),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)));
}
public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
{
var expr = Expression.Condition(
Compile(ternaryExpressionAst.Condition).Convert(typeof(bool)),
Compile(ternaryExpressionAst.IfTrue).Convert(typeof(object)),
Compile(ternaryExpressionAst.IfFalse).Convert(typeof(object)));
return expr;
}
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
{
object constantValue;
if (!CompilingConstantExpression && IsConstantValueVisitor.IsConstant(binaryExpressionAst, out constantValue))
{
return Expression.Constant(constantValue);
}
DynamicMetaObjectBinder binder;
var lhs = CompileExpressionOperand(binaryExpressionAst.Left);
var rhs = CompileExpressionOperand(binaryExpressionAst.Right);
switch (binaryExpressionAst.Operator)
{
case TokenKind.And:
return Expression.AndAlso(lhs.Convert(typeof(bool)), rhs.Convert(typeof(bool)));
case TokenKind.Or:
return Expression.OrElse(lhs.Convert(typeof(bool)), rhs.Convert(typeof(bool)));
case TokenKind.Is:
case TokenKind.IsNot:
if (rhs is ConstantExpression && rhs.Type == typeof(Type))
{
var isType = (Type)((ConstantExpression)rhs).Value;
if (isType != typeof(PSCustomObject) && isType != typeof(PSObject))
{
lhs = lhs.Type.IsValueType ? lhs : Expression.Call(CachedReflectionInfo.PSObject_Base, lhs);
if (binaryExpressionAst.Operator == TokenKind.Is)
{
return Expression.TypeIs(lhs, isType);
}
return Expression.Not(Expression.TypeIs(lhs, isType));
}
}
Expression result = Expression.Call(CachedReflectionInfo.TypeOps_IsInstance, lhs.Cast(typeof(object)), rhs.Cast(typeof(object)));
if (binaryExpressionAst.Operator == TokenKind.IsNot)
{
result = Expression.Not(result);
}
return result;
case TokenKind.As:
return Expression.Call(CachedReflectionInfo.TypeOps_AsOperator, lhs.Cast(typeof(object)), rhs.Convert(typeof(Type)));
case TokenKind.DotDot:
// We could generate faster code using Expression.Dynamic with a binder.
// Currently, type checks are done in ParserOps.RangeOperator at runtime every time
// a range operator is used. By replacing with Expression.Dynamic and a binder, the
// type check is done only once when you repeatedly execute the same line in script.
return Expression.Call(
CachedReflectionInfo.ParserOps_RangeOperator, lhs.Cast(typeof(object)), rhs.Cast(typeof(object)));
case TokenKind.Multiply:
if (lhs.Type == typeof(double) && rhs.Type == typeof(double))
{
return Expression.Multiply(lhs, rhs);
}
binder = PSBinaryOperationBinder.Get(ExpressionType.Multiply);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Divide:
if (lhs.Type == typeof(double) && rhs.Type == typeof(double))
{
return Expression.Divide(lhs, rhs);
}
binder = PSBinaryOperationBinder.Get(ExpressionType.Divide);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Rem:
binder = PSBinaryOperationBinder.Get(ExpressionType.Modulo);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Plus:
if (lhs.Type == typeof(double) && rhs.Type == typeof(double))
{
return Expression.Add(lhs, rhs);
}
binder = PSBinaryOperationBinder.Get(ExpressionType.Add);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Minus:
if (lhs.Type == typeof(double) && rhs.Type == typeof(double))
{
return Expression.Subtract(lhs, rhs);
}
binder = PSBinaryOperationBinder.Get(ExpressionType.Subtract);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Format:
if (lhs.Type != typeof(string))
{
lhs = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), lhs, s_executionContextParameter);
}
return Expression.Call(CachedReflectionInfo.StringOps_FormatOperator, lhs, rhs.Cast(typeof(object)));
case TokenKind.Xor:
return Expression.NotEqual(lhs.Convert(typeof(bool)), rhs.Convert(typeof(bool)));
case TokenKind.Shl:
binder = PSBinaryOperationBinder.Get(ExpressionType.LeftShift);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Shr:
binder = PSBinaryOperationBinder.Get(ExpressionType.RightShift);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Band:
binder = PSBinaryOperationBinder.Get(ExpressionType.And);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Bor:
binder = PSBinaryOperationBinder.Get(ExpressionType.Or);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Bxor:
binder = PSBinaryOperationBinder.Get(ExpressionType.ExclusiveOr);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Join:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_JoinOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)));
case TokenKind.Ieq:
binder = PSBinaryOperationBinder.Get(ExpressionType.Equal);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Ine:
binder = PSBinaryOperationBinder.Get(ExpressionType.NotEqual);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Ige:
binder = PSBinaryOperationBinder.Get(ExpressionType.GreaterThanOrEqual);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Igt:
binder = PSBinaryOperationBinder.Get(ExpressionType.GreaterThan);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Ilt:
binder = PSBinaryOperationBinder.Get(ExpressionType.LessThan);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Ile:
binder = PSBinaryOperationBinder.Get(ExpressionType.LessThanOrEqual);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Ilike:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_LikeOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
GetLikeRHSOperand(WildcardOptions.IgnoreCase, rhs).Cast(typeof(object)),
Expression.Constant(binaryExpressionAst.Operator));
case TokenKind.Inotlike:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_LikeOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
GetLikeRHSOperand(WildcardOptions.IgnoreCase, rhs).Cast(typeof(object)),
Expression.Constant(binaryExpressionAst.Operator));
case TokenKind.Imatch:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_MatchOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(false),
ExpressionCache.Constant(true));
case TokenKind.Inotmatch:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_MatchOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(true),
ExpressionCache.Constant(true));
case TokenKind.Ireplace:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_ReplaceOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(true));
case TokenKind.Icontains:
return GenerateCallContains(lhs, rhs, true);
case TokenKind.Inotcontains:
return Expression.Not(GenerateCallContains(lhs, rhs, true));
case TokenKind.Iin:
return GenerateCallContains(rhs, lhs, true);
case TokenKind.Inotin:
return Expression.Not(GenerateCallContains(rhs, lhs, true));
case TokenKind.Isplit:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_SplitOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(true));
case TokenKind.Ceq:
binder = PSBinaryOperationBinder.Get(ExpressionType.Equal, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Cne:
binder = PSBinaryOperationBinder.Get(ExpressionType.NotEqual, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Cge:
binder = PSBinaryOperationBinder.Get(ExpressionType.GreaterThanOrEqual, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Cgt:
binder = PSBinaryOperationBinder.Get(ExpressionType.GreaterThan, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Clt:
binder = PSBinaryOperationBinder.Get(ExpressionType.LessThan, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Cle:
binder = PSBinaryOperationBinder.Get(ExpressionType.LessThanOrEqual, false);
return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs);
case TokenKind.Clike:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_LikeOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
GetLikeRHSOperand(WildcardOptions.None, rhs).Cast(typeof(object)),
Expression.Constant(binaryExpressionAst.Operator));
case TokenKind.Cnotlike:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_LikeOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
GetLikeRHSOperand(WildcardOptions.None, rhs).Cast(typeof(object)),
Expression.Constant(binaryExpressionAst.Operator));
case TokenKind.Cmatch:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_MatchOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(false),
ExpressionCache.Constant(false));
case TokenKind.Cnotmatch:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_MatchOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(true),
ExpressionCache.Constant(false));
case TokenKind.Creplace:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_ReplaceOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(false));
case TokenKind.Ccontains:
return GenerateCallContains(lhs, rhs, false);
case TokenKind.Cnotcontains:
return Expression.Not(GenerateCallContains(lhs, rhs, false));
case TokenKind.Cin:
return GenerateCallContains(rhs, lhs, false);
case TokenKind.Cnotin:
return Expression.Not(GenerateCallContains(rhs, lhs, false));
case TokenKind.Csplit:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_SplitOperator,
s_executionContextParameter,
Expression.Constant(binaryExpressionAst.ErrorPosition),
lhs.Cast(typeof(object)),
rhs.Cast(typeof(object)),
ExpressionCache.Constant(false));
case TokenKind.QuestionQuestion:
return Coalesce(lhs, rhs);
}
throw new InvalidOperationException("Unknown token in binary operator.");
}
private static Expression GetLikeRHSOperand(WildcardOptions options, Expression expr)
{
if (!(expr is ConstantExpression constExpr))
{
return expr;
}
if (!(constExpr.Value is string val))
{
return expr;
}
return Expression.Constant(WildcardPattern.Get(val, options));
}
public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
{
object constantValue;
if (!CompilingConstantExpression && IsConstantValueVisitor.IsConstant(unaryExpressionAst, out constantValue))
{
return Expression.Constant(constantValue);
}
ExpressionAst child = unaryExpressionAst.Child;
switch (unaryExpressionAst.TokenKind)
{
case TokenKind.Exclaim:
case TokenKind.Not:
return DynamicExpression.Dynamic(PSUnaryOperationBinder.Get(ExpressionType.Not), typeof(object), CompileExpressionOperand(child));
case TokenKind.Minus:
return DynamicExpression.Dynamic(
PSBinaryOperationBinder.Get(ExpressionType.Subtract),
typeof(object),
ExpressionCache.Constant(0),
CompileExpressionOperand(child));
case TokenKind.Plus:
return DynamicExpression.Dynamic(
PSBinaryOperationBinder.Get(ExpressionType.Add),
typeof(object),
ExpressionCache.Constant(0),
CompileExpressionOperand(child));
case TokenKind.Bnot:
return DynamicExpression.Dynamic(
PSUnaryOperationBinder.Get(ExpressionType.OnesComplement),
typeof(object),
CompileExpressionOperand(child));
case TokenKind.PlusPlus:
return CompileIncrementOrDecrement(child, 1, true);
case TokenKind.MinusMinus:
return CompileIncrementOrDecrement(child, -1, true);
case TokenKind.PostfixPlusPlus:
return CompileIncrementOrDecrement(child, 1, false);
case TokenKind.PostfixMinusMinus:
return CompileIncrementOrDecrement(child, -1, false);
case TokenKind.Join:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_UnaryJoinOperator,
s_executionContextParameter,
Expression.Constant(unaryExpressionAst.Extent),
(CompileExpressionOperand(child)).Cast(typeof(object)));
case TokenKind.Isplit:
case TokenKind.Csplit:
// TODO: replace this with faster code
return Expression.Call(
CachedReflectionInfo.ParserOps_UnarySplitOperator,
s_executionContextParameter,
Expression.Constant(unaryExpressionAst.Extent),
(CompileExpressionOperand(child)).Cast(typeof(object)));
}
throw new InvalidOperationException("Unknown token in unary operator.");
}
private Expression CompileIncrementOrDecrement(ExpressionAst exprAst, int valueToAdd, bool prefix)
{
var av = ((ISupportsAssignment)exprAst).GetAssignableValue();
List<ParameterExpression> temps = new List<ParameterExpression>();
List<Expression> exprs = new List<Expression>();
ParameterExpression tmp;
Expression beforeVal = av.GetValue(this, exprs, temps);
if (prefix)
{
var newValue = DynamicExpression.Dynamic(
PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement),
typeof(object),
beforeVal);
tmp = Expression.Parameter(newValue.Type);
exprs.Add(Expression.Assign(tmp, newValue));
exprs.Add(av.SetValue(this, tmp));
exprs.Add(tmp);
}
else
{
tmp = Expression.Parameter(beforeVal.Type);
exprs.Add(Expression.Assign(tmp, beforeVal));
var newValue = DynamicExpression.Dynamic(
PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement),
typeof(object),
tmp);
exprs.Add(av.SetValue(this, newValue));
if (tmp.Type.IsValueType)
{
// This is the result of the expression - it might be unused, but we don't bother knowing if it is used or not.
exprs.Add(tmp);
}
else
{
// For backwards compatibility, return 0 when the pre-incremented value was null. We do the check after
// the increment because this value isn't always used, so it might be removed as dead code, and we can
// thus avoid adding an extra if test in some common cases (e.g. a for loop frequently uses ++ as the iteration
// expression.
exprs.Add(Expression.Condition(
Expression.Equal(tmp, ExpressionCache.NullConstant),
ExpressionCache.Constant(0).Cast(typeof(object)),
tmp));
}
}
temps.Add(tmp);
return Expression.Block(temps, exprs);
}
public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
{
object constantValue;
if (!CompilingConstantExpression && IsConstantValueVisitor.IsConstant(convertExpressionAst, out constantValue))
{
return Expression.Constant(constantValue);
}
var typeName = convertExpressionAst.Type.TypeName;
var hashTableAst = convertExpressionAst.Child as HashtableAst;
Expression childExpr = null;
if (hashTableAst != null)
{
var temp = NewTemp(typeof(OrderedDictionary), "orderedDictionary");
if (typeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase))
{
return Expression.Block(
typeof(OrderedDictionary),
new[] { temp },
BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
}
if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase))
{
// pure laziness here - we should construct the PSObject directly. Instead, we're relying on the conversion
// to create the PSObject from an OrderedDictionary.
childExpr = Expression.Block(
typeof(OrderedDictionary),
new[] { temp },
BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
}
}
if (convertExpressionAst.IsRef())
{
var varExpr = convertExpressionAst.Child as VariableExpressionAst;
if (varExpr != null && varExpr.VariablePath.IsVariable && !varExpr.IsConstantVariable())
{
// We'll wrap the variable in a PSReference, but not the constant variables ($true, $false, $null) because those
// can't be changed.
IEnumerable<PropertyInfo> unused1;
bool unused2;
var varType = varExpr.GetVariableType(this, out unused1, out unused2);
return Expression.Call(
CachedReflectionInfo.VariableOps_GetVariableAsRef,
Expression.Constant(varExpr.VariablePath),
s_executionContextParameter,
varType != null && varType != typeof(object)
? Expression.Constant(varType, typeof(Type))
: ExpressionCache.NullType);
}
}
if (childExpr == null)
{
childExpr = Compile(convertExpressionAst.Child);
}
if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase))
{
// We can't use the normal PSConvertBinder because it is strongly typed, and we
// play some funny games with the PSCustomObject type (externally, it's just an
// alias for PSObject, internally we do other stuff with it.)
return DynamicExpression.Dynamic(PSCustomObjectConverter.Get(), typeof(object), childExpr);
}
return ConvertValue(convertExpressionAst.Type, childExpr);
}
public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
{
return Expression.Constant(constantExpressionAst.Value);
}
public object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
{
return Expression.Constant(stringConstantExpressionAst.Value);
}
public object VisitSubExpression(SubExpressionAst subExpressionAst)
{
if (subExpressionAst.SubExpression.Statements.Count == 0)
{
// This seems wrong, but is compatible with V2.
return ExpressionCache.NullConstant;
}
// SubExpression and ParenExpression are two special cases for handling the partial output while exception
// is thrown. For example, the output of $(1; throw 2) should be 1 and the error record with message '2';
// but the output of $(1; throw 2).Length should just be the error record with message '2'.
bool shouldPreserveResultInCaseofException = subExpressionAst.ShouldPreserveOutputInCaseOfException();
return CaptureAstResults(
subExpressionAst.SubExpression,
shouldPreserveResultInCaseofException
? CaptureAstContext.AssignmentWithResultPreservation
: CaptureAstContext.AssignmentWithoutResultPreservation);
}
public object VisitUsingExpression(UsingExpressionAst usingExpression)
{
string usingExprKey = PsUtils.GetUsingExpressionKey(usingExpression);
return Expression.Call(
CachedReflectionInfo.VariableOps_GetUsingValue, LocalVariablesParameter,
Expression.Constant(usingExprKey),
ExpressionCache.Constant(usingExpression.RuntimeUsingIndex),
s_executionContextParameter);
}
public object VisitVariableExpression(VariableExpressionAst variableExpressionAst)
{
var varPath = variableExpressionAst.VariablePath;
if (varPath.IsVariable)
{
// Generate constants for variables that really are constant.
if (varPath.UnqualifiedPath.Equals(SpecialVariables.Null, StringComparison.OrdinalIgnoreCase))
{
return ExpressionCache.NullConstant;
}
if (varPath.UnqualifiedPath.Equals(SpecialVariables.True, StringComparison.OrdinalIgnoreCase))
{
return ExpressionCache.Constant(true);
}
if (varPath.UnqualifiedPath.Equals(SpecialVariables.False, StringComparison.OrdinalIgnoreCase))
{
return ExpressionCache.Constant(false);
}
}
int tupleIndex = variableExpressionAst.TupleIndex;
if (variableExpressionAst.Automatic)
{
if (variableExpressionAst.VariablePath.UnqualifiedPath.Equals(SpecialVariables.Question, StringComparison.OrdinalIgnoreCase))
{
if (Optimize)
{
return Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue);
}
// Unoptimized - need to check for breakpoints, so just get the variable, etc.
return CallGetVariable(Expression.Constant(variableExpressionAst.VariablePath), variableExpressionAst);
}
return GetAutomaticVariable(variableExpressionAst);
}
if (tupleIndex < 0)
{
return CallGetVariable(Expression.Constant(variableExpressionAst.VariablePath), variableExpressionAst);
}
return GetLocal(tupleIndex);
}
internal Expression CompileTypeName(ITypeName typeName, IScriptExtent errorPos)
{
Type type;
try
{
// If creating the type throws an exception, just defer that error until runtime.
type = typeName.GetReflectionType();
}
catch (Exception)
{
type = null;
}
if (type != null)
{
return Expression.Constant(type, typeof(Type));
}
return Expression.Call(
CachedReflectionInfo.TypeOps_ResolveTypeName,
Expression.Constant(typeName),
Expression.Constant(errorPos));
}
public object VisitTypeExpression(TypeExpressionAst typeExpressionAst)
{
return CompileTypeName(typeExpressionAst.TypeName, typeExpressionAst.Extent);
}
public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
{
// Getting a static member is much simpler because we can ignore instance
// and type table members and we only have one adapter - .Net. So we'll
// avoid a dynamic expression if possible.
if (memberExpressionAst.Static && (memberExpressionAst.Expression is TypeExpressionAst))
{
var type = ((TypeExpressionAst)memberExpressionAst.Expression).TypeName.GetReflectionType();
if (type != null && !type.IsGenericTypeDefinition)
{
var member = memberExpressionAst.Member as StringConstantExpressionAst;
if (member != null)
{
// We skip Methods because the adapter wraps them in a PSMethod and it's not a common scenario.
var memberInfo = type.GetMember(
member.Value,
MemberTypes.Field | MemberTypes.Property,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (memberInfo.Length == 1)
{
var propertyInfo = memberInfo[0] as PropertyInfo;
if (propertyInfo != null)
{
if (propertyInfo.PropertyType.IsByRefLike)
{
// ByRef-like types are not boxable and should be used only on stack.
return Expression.Throw(
Expression.New(
CachedReflectionInfo.GetValueException_ctor,
Expression.Constant(nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField)),
Expression.Constant(null, typeof(Exception)),
Expression.Constant(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField),
Expression.NewArrayInit(
typeof(object),
Expression.Constant(propertyInfo.Name),
Expression.Constant(propertyInfo.PropertyType, typeof(Type)))),
typeof(object));
}
if (propertyInfo.CanRead)
{
return Expression.Property(null, propertyInfo);
}
// else let the dynamic site generate the error - this is rare anyway
}
else
{
// Field cannot be of a ByRef-like type unless it's an instance member of a ref struct.
// So we don't need to check 'IsByRefLike' for static field access.
return Expression.Field(null, (FieldInfo)memberInfo[0]);
}
}
}
}
}
var target = CompileExpressionOperand(memberExpressionAst.Expression);
// If the ?. operator is used for null conditional check, add the null conditional expression.
var memberNameAst = memberExpressionAst.Member as StringConstantExpressionAst;
Expression memberAccessExpr = memberNameAst != null
? DynamicExpression.Dynamic(PSGetMemberBinder.Get(memberNameAst.Value, _memberFunctionType, memberExpressionAst.Static), typeof(object), target)
: DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(_memberFunctionType, memberExpressionAst.Static), typeof(object), target, Compile(memberExpressionAst.Member));
return memberExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(target, memberAccessExpr) : memberAccessExpr;
}
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst)
{
var arguments = invokeMemberExpressionAst.Arguments;
var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression);
return CombineTypeConstraintForMethodResolution(
targetTypeConstraint,
arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
}
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCtorInvokeMemberExpressionAst invokeMemberExpressionAst)
{
Type targetTypeConstraint = null;
var arguments = invokeMemberExpressionAst.Arguments;
TypeDefinitionAst typeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(invokeMemberExpressionAst);
if (typeDefinitionAst != null)
{
targetTypeConstraint = (typeDefinitionAst as TypeDefinitionAst).Type.BaseType;
}
else
{
Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst");
}
return CombineTypeConstraintForMethodResolution(
targetTypeConstraint,
arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
}
internal Expression InvokeMember(
string name,
PSMethodInvocationConstraints constraints,
Expression target,
IEnumerable<Expression> args,
bool @static,
bool propertySet,
bool nullConditional = false)
{
var callInfo = new CallInfo(args.Count());
var classScope = _memberFunctionType?.Type;
var binder = name.Equals("new", StringComparison.OrdinalIgnoreCase) && @static
? (CallSiteBinder)PSCreateInstanceBinder.Get(callInfo, constraints, publicTypeOnly: true)
: PSInvokeMemberBinder.Get(name, callInfo, @static, propertySet, constraints, classScope);
var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target));
return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder;
}
private static Expression InvokeBaseCtorMethod(PSMethodInvocationConstraints constraints, Expression target, IEnumerable<Expression> args)
{
var callInfo = new CallInfo(args.Count());
var binder = PSInvokeBaseCtorBinder.Get(callInfo, constraints);
return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target));
}
internal Expression InvokeDynamicMember(
Expression memberNameExpr,
PSMethodInvocationConstraints constraints,
Expression target,
IEnumerable<Expression> args,
bool @static,
bool propertySet,
bool nullConditional = false)
{
var binder = PSInvokeDynamicMemberBinder.Get(new CallInfo(args.Count()), _memberFunctionType, @static, propertySet, constraints);
var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(memberNameExpr).Prepend(target));
return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder;
}
public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
{
var constraints = GetInvokeMemberConstraints(invokeMemberExpressionAst);
var target = CompileExpressionOperand(invokeMemberExpressionAst.Expression);
var args = CompileInvocationArguments(invokeMemberExpressionAst.Arguments);
var memberNameAst = invokeMemberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
return InvokeMember(
memberNameAst.Value,
constraints,
target,
args,
invokeMemberExpressionAst.Static,
propertySet: false,
invokeMemberExpressionAst.NullConditional);
}
var memberNameExpr = Compile(invokeMemberExpressionAst.Member);
return InvokeDynamicMember(memberNameExpr, constraints, target, args, invokeMemberExpressionAst.Static, propertySet: false, invokeMemberExpressionAst.NullConditional);
}
public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
{
Expression values = null;
ExpressionAst pureExprAst = null;
var subExpr = arrayExpressionAst.SubExpression;
if (subExpr.Traps == null)
{
if (subExpr.Statements.Count == 1)
{
var pipelineBase = subExpr.Statements[0] as PipelineBaseAst;
if (pipelineBase != null)
{
pureExprAst = pipelineBase.GetPureExpression();
if (pureExprAst != null)
{
values = Compile(pureExprAst);
}
}
}
else if (subExpr.Statements.Count == 0)
{
// A dynamic site can't take void - but a void value is just an empty array.
return Expression.NewArrayInit(typeof(object));
}
}
values ??= CaptureAstResults(subExpr, CaptureAstContext.Enumerable);
if (pureExprAst is ArrayLiteralAst)
{
// If the pure expression is ArrayLiteralAst, just return the result.
return values;
}
if (values.Type.IsPrimitive || values.Type == typeof(string))
{
// Slight optimization - no need for a dynamic site. We could special case other
// types as well, but it's probably not worth it.
return Expression.NewArrayInit(typeof(object), values.Cast(typeof(object)));
}
if (values.Type == typeof(void))
{
// A dynamic site can't take void - but a void value is just an empty array.
return Expression.Block(values, Expression.NewArrayInit(typeof(object)));
}
return DynamicExpression.Dynamic(PSToObjectArrayBinder.Get(), typeof(object[]), values);
}
public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
{
List<Expression> elementValues = new List<Expression>(arrayLiteralAst.Elements.Count);
foreach (var element in arrayLiteralAst.Elements)
{
var elementValue = Compile(element);
elementValues.Add(elementValue.Type != typeof(void) ? elementValue.Cast(typeof(object)) : Expression.Block(elementValue, ExpressionCache.AutomationNullConstant));
}
return Expression.NewArrayInit(typeof(object), elementValues);
}
private IEnumerable<Expression> BuildHashtable(ReadOnlyCollection<KeyValuePair> keyValuePairs, ParameterExpression temp, bool ordered)
{
yield return Expression.Assign(temp,
Expression.New(ordered ? CachedReflectionInfo.OrderedDictionary_ctor : CachedReflectionInfo.Hashtable_ctor,
ExpressionCache.Constant(keyValuePairs.Count),
ExpressionCache.OrdinalIgnoreCaseComparer.Cast(typeof(IEqualityComparer))));
for (int index = 0; index < keyValuePairs.Count; index++)
{
var keyValuePair = keyValuePairs[index];
Expression key = Expression.Convert(Compile(keyValuePair.Item1), typeof(object));
// We should not preserve the partial output if exception is thrown when evaluating the value.
Expression value =
Expression.Convert(CaptureStatementResults(keyValuePair.Item2, CaptureAstContext.AssignmentWithoutResultPreservation), typeof(object));
Expression errorExtent = Expression.Constant(keyValuePair.Item1.Extent);
yield return Expression.Call(CachedReflectionInfo.HashtableOps_AddKeyValuePair, temp, key, value, errorExtent);
}
yield return temp;
}
public object VisitHashtable(HashtableAst hashtableAst)
{
var temp = NewTemp(typeof(Hashtable), "hashtable");
return Expression.Block(
typeof(Hashtable),
new[] { temp },
BuildHashtable(hashtableAst.KeyValuePairs, temp, ordered: false));
}
public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
{
// The script block is not visited until it is executed, executing a script block expression node simply
// builds a new script block without analyzing it.
//
// A wrapper is returned so we can cache the script block and return clones that are pre-compiled if
// the expression is evaluated more than once (e.g. in a loop, or if the containing function/script
// is called again.)
return Expression.Call(
Expression.Constant(new ScriptBlockExpressionWrapper(scriptBlockExpressionAst.ScriptBlock)),
CachedReflectionInfo.ScriptBlockExpressionWrapper_GetScriptBlock,
s_executionContextParameter,
ExpressionCache.Constant(false));
}
public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
{
var pipe = parenExpressionAst.Pipeline;
var assignmentStatementAst = pipe as AssignmentStatementAst;
if (assignmentStatementAst != null)
{
return CompileAssignment(assignmentStatementAst);
}
// SubExpression and ParenExpression are two special cases for handling the partial output while exception
// is thrown. For example, function bar { 1; throw 2 }, the output of (bar) should be 1 and the error record with message '2';
// but the output of (bar).Length should just be the error record with message '2'.
bool shouldPreserveOutputInCaseOfException = parenExpressionAst.ShouldPreserveOutputInCaseOfException();
return CaptureStatementResults(
pipe,
shouldPreserveOutputInCaseOfException
? CaptureAstContext.AssignmentWithResultPreservation
: CaptureAstContext.AssignmentWithoutResultPreservation);
}
public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
{
var left = Expression.Constant(expandableStringExpressionAst.FormatExpression);
var nestedAsts = expandableStringExpressionAst.NestedExpressions;
var toStringBinder = PSToStringBinder.Get();
var right = Expression.NewArrayInit(
typeof(string),
nestedAsts.Select(e => DynamicExpression.Dynamic(toStringBinder, typeof(string), Compile(e), s_executionContextParameter)));
return Expression.Call(CachedReflectionInfo.StringOps_FormatOperator, left, right);
}
public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
{
var targetExpr = CompileExpressionOperand(indexExpressionAst.Target);
var index = indexExpressionAst.Index;
var arrayLiteral = (index as ArrayLiteralAst);
var constraints = CombineTypeConstraintForMethodResolution(
GetTypeConstraintForMethodResolution(indexExpressionAst.Target),
GetTypeConstraintForMethodResolution(index));
// An array literal is either:
// $x[1,2]
// or
// $x[,1]
// In the former case, the user is requesting an array slice. In the latter case, they index expression is likely
// an array (dynamically determined) and they don't want an array slice, they want to use the array as the index
// expression.
Expression indexingExpr = arrayLiteral != null && arrayLiteral.Elements.Count > 1
? DynamicExpression.Dynamic(
PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints),
typeof(object),
arrayLiteral.Elements.Select(CompileExpressionOperand).Prepend(targetExpr))
: DynamicExpression.Dynamic(
PSGetIndexBinder.Get(argCount: 1, constraints),
typeof(object),
targetExpr,
CompileExpressionOperand(index));
return indexExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(targetExpr, indexingExpr) : indexingExpr;
}
private static Expression GetNullConditionalWrappedExpression(Expression targetExpr, Expression memberAccessExpression)
{
return Expression.Condition(
Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNull, targetExpr.Cast(typeof(object))),
ExpressionCache.NullConstant,
memberAccessExpression);
}
public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
{
return attributedExpressionAst.Child.Accept(this);
}
public object VisitBlockStatement(BlockStatementAst blockStatementAst)
{
return null;
}
#endregion Expressions
}
internal class MemberAssignableValue : IAssignableValue
{
internal MemberExpressionAst MemberExpression { get; set; }
private Expression CachedTarget { get; set; }
private Expression CachedPropertyExpr { get; set; }
private Expression GetTargetExpr(Compiler compiler)
{
return compiler.Compile(MemberExpression.Expression);
}
private Expression GetPropertyExpr(Compiler compiler)
{
return compiler.Compile(MemberExpression.Member);
}
public Expression GetValue(Compiler compiler, List<Expression> exprs, List<ParameterExpression> temps)
{
var target = GetTargetExpr(compiler);
var targetTemp = Expression.Parameter(target.Type);
temps.Add(targetTemp);
CachedTarget = targetTemp;
exprs.Add(Expression.Assign(targetTemp, target));
var memberNameAst = MemberExpression.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
string name = memberNameAst.Value;
return DynamicExpression.Dynamic(PSGetMemberBinder.Get(name, compiler._memberFunctionType, MemberExpression.Static), typeof(object), targetTemp);
}
var propertyExpr = GetPropertyExpr(compiler);
var propertyNameTemp = Expression.Parameter(propertyExpr.Type);
temps.Add(propertyNameTemp);
exprs.Add(Expression.Assign(propertyNameTemp, compiler.Compile(MemberExpression.Member)));
CachedPropertyExpr = propertyNameTemp;
return DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(compiler._memberFunctionType, MemberExpression.Static), typeof(object), targetTemp, propertyNameTemp);
}
public Expression SetValue(Compiler compiler, Expression rhs)
{
var memberNameAst = MemberExpression.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
string name = memberNameAst.Value;
return DynamicExpression.Dynamic(
PSSetMemberBinder.Get(name, compiler._memberFunctionType, MemberExpression.Static),
typeof(object),
CachedTarget ?? GetTargetExpr(compiler), rhs);
}
return DynamicExpression.Dynamic(
PSSetDynamicMemberBinder.Get(compiler._memberFunctionType, MemberExpression.Static),
typeof(object),
CachedTarget ?? GetTargetExpr(compiler),
CachedPropertyExpr ?? GetPropertyExpr(compiler), rhs);
}
}
internal class InvokeMemberAssignableValue : IAssignableValue
{
internal InvokeMemberExpressionAst InvokeMemberExpressionAst { get; set; }
private ParameterExpression _targetExprTemp;
private ParameterExpression _memberNameExprTemp;
private IEnumerable<ParameterExpression> _argExprTemps;
private Expression GetTargetExpr(Compiler compiler)
{
return _targetExprTemp ?? compiler.Compile(InvokeMemberExpressionAst.Expression);
}
private Expression GetMemberNameExpr(Compiler compiler)
{
return _memberNameExprTemp ?? compiler.Compile(InvokeMemberExpressionAst.Member);
}
private IEnumerable<Expression> GetArgumentExprs(Compiler compiler)
{
if (_argExprTemps != null)
{
return _argExprTemps;
}
ReadOnlyCollection<ExpressionAst> arguments = InvokeMemberExpressionAst.Arguments;
if (arguments is null || arguments.Count == 0)
{
return Array.Empty<Expression>();
}
var result = new Expression[arguments.Count];
for (int i = 0; i < result.Length; i++)
{
result[i] = compiler.Compile(arguments[i]);
}
return result;
}
public Expression GetValue(Compiler compiler, List<Expression> exprs, List<ParameterExpression> temps)
{
var constraints = Compiler.GetInvokeMemberConstraints(InvokeMemberExpressionAst);
var targetExpr = GetTargetExpr(compiler);
_targetExprTemp = Expression.Variable(targetExpr.Type);
exprs.Add(Expression.Assign(_targetExprTemp, targetExpr));
int exprsIndex = exprs.Count;
var args = GetArgumentExprs(compiler);
_argExprTemps = args.Select(static arg => Expression.Variable(arg.Type)).ToArray();
exprs.AddRange(args.Zip(_argExprTemps, static (arg, temp) => Expression.Assign(temp, arg)));
temps.Add(_targetExprTemp);
int tempsIndex = temps.Count;
temps.AddRange(_argExprTemps);
var memberNameAst = InvokeMemberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
return compiler.InvokeMember(memberNameAst.Value, constraints, _targetExprTemp, _argExprTemps, @static: false, propertySet: false);
}
var memberNameExpr = GetMemberNameExpr(compiler);
_memberNameExprTemp = Expression.Variable(memberNameExpr.Type);
exprs.Insert(exprsIndex, Expression.Assign(_memberNameExprTemp, memberNameExpr));
temps.Insert(tempsIndex, _memberNameExprTemp);
return compiler.InvokeDynamicMember(_memberNameExprTemp, constraints, _targetExprTemp, _argExprTemps, @static: false, propertySet: false);
}
public Expression SetValue(Compiler compiler, Expression rhs)
{
var constraints = Compiler.GetInvokeMemberConstraints(InvokeMemberExpressionAst);
var memberNameAst = InvokeMemberExpressionAst.Member as StringConstantExpressionAst;
var target = GetTargetExpr(compiler);
var args = GetArgumentExprs(compiler);
if (memberNameAst != null)
{
return compiler.InvokeMember(memberNameAst.Value, constraints, target, args.Append(rhs), @static: false, propertySet: true);
}
var memberNameExpr = GetMemberNameExpr(compiler);
return compiler.InvokeDynamicMember(memberNameExpr, constraints, target, args.Append(rhs), @static: false, propertySet: true);
}
}
internal class IndexAssignableValue : IAssignableValue
{
internal IndexExpressionAst IndexExpressionAst { get; set; }
private ParameterExpression _targetExprTemp;
private ParameterExpression _indexExprTemp;
private PSMethodInvocationConstraints GetInvocationConstraints()
{
return Compiler.CombineTypeConstraintForMethodResolution(
Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Target),
Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Index));
}
private Expression GetTargetExpr(Compiler compiler)
{
return _targetExprTemp ?? compiler.Compile(IndexExpressionAst.Target);
}
private Expression GetIndexExpr(Compiler compiler)
{
return _indexExprTemp ?? compiler.Compile(IndexExpressionAst.Index);
}
public Expression GetValue(Compiler compiler, List<Expression> exprs, List<ParameterExpression> temps)
{
var targetExpr = compiler.Compile(IndexExpressionAst.Target);
_targetExprTemp = Expression.Variable(targetExpr.Type);
temps.Add(_targetExprTemp);
exprs.Add(Expression.Assign(_targetExprTemp, targetExpr));
var index = IndexExpressionAst.Index;
var arrayLiteral = index as ArrayLiteralAst;
var constraints = GetInvocationConstraints();
Expression result;
if (arrayLiteral != null)
{
// If assignment to slices were allowed, we'd need to save the elements in temps
// like we do when doing normal assignment (below). But it's not allowed, so it
// doesn't matter.
result = DynamicExpression.Dynamic(PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints),
typeof(object),
arrayLiteral.Elements.Select(compiler.Compile).Prepend(_targetExprTemp));
}
else
{
var indexExpr = compiler.Compile(index);
_indexExprTemp = Expression.Variable(indexExpr.Type);
temps.Add(_indexExprTemp);
exprs.Add(Expression.Assign(_indexExprTemp, indexExpr));
result = DynamicExpression.Dynamic(PSGetIndexBinder.Get(1, constraints), typeof(object), _targetExprTemp, _indexExprTemp);
}
return result;
}
public Expression SetValue(Compiler compiler, Expression rhs)
{
// Save the rhs for multi-assign, i.e. $x = $y[0] = 2
// This seems wrong though - the value $y[0] should be assigned to $x, not 2.
// It's not often they would be different values, but if a conversion is involved, they could
// be. At any rate, V2 did it this way, so we do as well.
var temp = Expression.Variable(rhs.Type);
var index = IndexExpressionAst.Index;
var arrayLiteral = index as ArrayLiteralAst;
var constraints = GetInvocationConstraints();
var targetExpr = GetTargetExpr(compiler);
Expression setExpr;
if (arrayLiteral != null)
{
setExpr = DynamicExpression.Dynamic(
PSSetIndexBinder.Get(arrayLiteral.Elements.Count, constraints),
typeof(object),
arrayLiteral.Elements.Select(compiler.Compile).Prepend(targetExpr).Append(temp));
}
else
{
setExpr = DynamicExpression.Dynamic(
PSSetIndexBinder.Get(1, constraints),
typeof(object),
targetExpr,
GetIndexExpr(compiler),
temp);
}
return Expression.Block(new[] { temp }, Expression.Assign(temp, rhs), setExpr, temp);
}
}
internal class ArrayAssignableValue : IAssignableValue
{
internal ArrayLiteralAst ArrayLiteral { get; set; }
public Expression GetValue(Compiler compiler, List<Expression> exprs, List<ParameterExpression> temps)
{
Diagnostics.Assert(false, "Array assignment does not support read/modify/write operators");
return null;
}
public Expression SetValue(Compiler compiler, Expression rhs)
{
Diagnostics.Assert(rhs.Type == typeof(IList), "Code generation must ensure we have an IList to assign from.");
var rhsTemp = Expression.Variable(rhs.Type);
int count = ArrayLiteral.Elements.Count;
var exprs = new List<Expression>();
exprs.Add(Expression.Assign(rhsTemp, rhs));
for (int i = 0; i < count; ++i)
{
Expression indexedRHS = Expression.Call(rhsTemp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i));
var lhsElement = ArrayLiteral.Elements[i];
var nestedArrayLHS = lhsElement as ArrayLiteralAst;
var parenExpressionAst = lhsElement as ParenExpressionAst;
if (parenExpressionAst != null)
{
nestedArrayLHS = parenExpressionAst.Pipeline.GetPureExpression() as ArrayLiteralAst;
}
if (nestedArrayLHS != null)
{
// Need to turn the rhs into an IList
indexedRHS = DynamicExpression.Dynamic(PSArrayAssignmentRHSBinder.Get(nestedArrayLHS.Elements.Count), typeof(IList), indexedRHS);
}
exprs.Add(compiler.ReduceAssignment((ISupportsAssignment)lhsElement, TokenKind.Equals, indexedRHS));
}
// Add the temp as the last expression for chained assignment, i.e. $x = $y,$z = 1,2
exprs.Add(rhsTemp);
return Expression.Block(new[] { rhsTemp }, exprs);
}
}
internal class PowerShellLoopExpression : Expression, Interpreter.IInstructionProvider
{
public override bool CanReduce { get { return true; } }
public override Type Type { get { return typeof(void); } }
public override ExpressionType NodeType { get { return ExpressionType.Extension; } }
private readonly IEnumerable<Expression> _exprs;
internal PowerShellLoopExpression(IEnumerable<Expression> exprs)
{
_exprs = exprs;
}
public override Expression Reduce()
{
return Expression.Block(_exprs);
}
public void AddInstructions(LightCompiler compiler)
{
EnterLoopInstruction enterLoop = null;
compiler.PushLabelBlock(LabelScopeKind.Statement);
// emit loop body:
foreach (var expr in _exprs)
{
compiler.CompileAsVoid(expr);
var enterLoopExpression = expr as EnterLoopExpression;
if (enterLoopExpression != null)
{
enterLoop = enterLoopExpression.EnterLoopInstruction;
}
}
compiler.PopLabelBlock(LabelScopeKind.Statement);
// If enterLoop is null, we will never JIT compile the loop.
if (enterLoop != null)
{
enterLoop.FinishLoop(compiler.Instructions.Count);
}
}
}
internal class EnterLoopExpression : Expression, Interpreter.IInstructionProvider
{
public override bool CanReduce { get { return true; } }
public override Type Type { get { return typeof(void); } }
public override ExpressionType NodeType { get { return ExpressionType.Extension; } }
public override Expression Reduce()
{
return ExpressionCache.Empty;
}
internal new PowerShellLoopExpression Loop { get; set; }
internal EnterLoopInstruction EnterLoopInstruction { get; private set; }
internal int LoopStatementCount { get; set; }
public void AddInstructions(LightCompiler compiler)
{
// The EnterLoopInstruction is the instruction the interpreter uses to track the number of iterations a loop
// has executed and the instruction that kicks of the background compilation.
// If the statement count is too high, JIT takes too much time/resources to compile, so
// we just skip it. By not emitting a EnterLoopInstruction, we'll never attempt to JIT the loop body.
if (LoopStatementCount < 300)
{
// Start compilation after 16 iterations of the loop.
EnterLoopInstruction = new EnterLoopInstruction(Loop, compiler.Locals, 16, compiler.Instructions.Count);
compiler.Instructions.Emit(EnterLoopInstruction);
}
}
}
internal class UpdatePositionExpr : Expression, Interpreter.IInstructionProvider
{
public override bool CanReduce { get { return true; } }
public override Type Type { get { return typeof(void); } }
public override ExpressionType NodeType { get { return ExpressionType.Extension; } }
private readonly IScriptExtent _extent;
private readonly SymbolDocumentInfo _debugSymbolDocument;
private readonly int _sequencePoint;
private readonly bool _checkBreakpoints;
public UpdatePositionExpr(IScriptExtent extent, int sequencePoint, SymbolDocumentInfo debugSymbolDocument, bool checkBreakpoints)
{
_extent = extent;
_checkBreakpoints = checkBreakpoints;
_debugSymbolDocument = debugSymbolDocument;
_sequencePoint = sequencePoint;
}
public override Expression Reduce()
{
var exprs = new List<Expression>();
if (_debugSymbolDocument != null)
{
exprs.Add(Expression.DebugInfo(_debugSymbolDocument, _extent.StartLineNumber, _extent.StartColumnNumber, _extent.EndLineNumber, _extent.EndColumnNumber));
}
exprs.Add(
Expression.Assign(
Expression.Field(Compiler.s_functionContext, CachedReflectionInfo.FunctionContext__currentSequencePointIndex),
ExpressionCache.Constant(_sequencePoint)));
if (_checkBreakpoints)
{
exprs.Add(
Expression.IfThen(
Expression.GreaterThan(
Expression.Field(Compiler.s_executionContextParameter, CachedReflectionInfo.ExecutionContext_DebuggingMode),
ExpressionCache.Constant(0)),
Expression.Call(
Expression.Field(Compiler.s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger),
CachedReflectionInfo.Debugger_OnSequencePointHit,
Compiler.s_functionContext)));
}
exprs.Add(ExpressionCache.Empty);
return Expression.Block(exprs);
}
public void AddInstructions(LightCompiler compiler)
{
compiler.Instructions.Emit(UpdatePositionInstruction.Create(_sequencePoint, _checkBreakpoints));
}
}
}