diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index f84ce6e55a..1bcd9d79d9 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -400,35 +400,34 @@ namespace System.Management.Automation.Language internal static BindingRestrictions GetLanguageModeCheckIfHasEverUsedConstrainedLanguage() { - BindingRestrictions languageModeRestriction = BindingRestrictions.Empty; - // Also add a language mode check to detect toggling between language modes if (ExecutionContext.HasEverUsedConstrainedLanguage) { var context = LocalPipeline.GetExecutionContextFromTLS(); - switch (context.LanguageMode) - { - case PSLanguageMode.ConstrainedLanguage: - languageModeRestriction = BindingRestrictions.GetExpressionRestriction( - Expression.Equal( - Expression.Property( - ExpressionCache.GetExecutionContextFromTLS, - CachedReflectionInfo.ExecutionContext_LanguageMode), - Expression.Constant(PSLanguageMode.ConstrainedLanguage))); - break; - default: - languageModeRestriction = BindingRestrictions.GetExpressionRestriction( - Expression.NotEqual( - Expression.Property( - ExpressionCache.GetExecutionContextFromTLS, - CachedReflectionInfo.ExecutionContext_LanguageMode), - Expression.Constant(PSLanguageMode.ConstrainedLanguage))); - break; - } + var tmp = Expression.Variable(typeof(ExecutionContext)); + var langModeFromContext = Expression.Property(tmp, CachedReflectionInfo.ExecutionContext_LanguageMode); + var constrainedLanguageMode = Expression.Constant(PSLanguageMode.ConstrainedLanguage); + + // Execution context might be null if we're called from a thread with no runspace (e.g. a PSObject + // is used in some C# w/ dynamic). This is sometimes fine, we don't always need a runspace to access + // properties. + Expression test = context?.LanguageMode == PSLanguageMode.ConstrainedLanguage + ? Expression.AndAlso( + Expression.NotEqual(tmp, ExpressionCache.NullExecutionContext), + Expression.Equal(langModeFromContext, constrainedLanguageMode)) + : Expression.OrElse( + Expression.Equal(tmp, ExpressionCache.NullExecutionContext), + Expression.NotEqual(langModeFromContext, constrainedLanguageMode)); + + return BindingRestrictions.GetExpressionRestriction( + Expression.Block( + new[] {tmp}, + Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS), + test)); } - return languageModeRestriction; + return BindingRestrictions.Empty; } internal static BindingRestrictions GetOptionalVersionAndLanguageCheckForType(DynamicMetaObjectBinder binder, Type targetType, int expectedVersionNumber) @@ -5313,10 +5312,10 @@ namespace System.Management.Automation.Language new object[] { Name }); } - internal static DynamicMetaObject EnsureAllowedInLanguageMode(PSLanguageMode languageMode, DynamicMetaObject target, Object targetValue, + internal static DynamicMetaObject EnsureAllowedInLanguageMode(ExecutionContext context, DynamicMetaObject target, Object targetValue, string name, bool isStatic, DynamicMetaObject[] args, BindingRestrictions moreTests, string errorID, string resourceString) { - if (languageMode == PSLanguageMode.ConstrainedLanguage) + if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage) { if (!IsAllowedInConstrainedLanguage(targetValue, name, isStatic)) { @@ -5970,7 +5969,7 @@ namespace System.Management.Automation.Language // Validate that this is allowed in the current language mode var context = LocalPipeline.GetExecutionContextFromTLS(); DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode( - context.LanguageMode, target, targetValue, Name, _static, new[] { value }, restrictions, + context, target, targetValue, Name, _static, new[] { value }, restrictions, "PropertySetterNotSupportedInConstrainedLanguage", ParserStrings.PropertySetConstrainedLanguage); if (runtimeError != null) { @@ -6460,7 +6459,7 @@ namespace System.Management.Automation.Language // Validate that this is allowed in the current language mode var context = LocalPipeline.GetExecutionContextFromTLS(); DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode( - context.LanguageMode, target, targetValue, Name, _static, args, restrictions, + context, target, targetValue, Name, _static, args, restrictions, "MethodInvocationNotSupportedInConstrainedLanguage", ParserStrings.InvokeMethodConstrainedLanguage); if (runtimeError != null) { diff --git a/test/powershell/Language/Scripting/ConstrainedLanguageMode.Tests.ps1 b/test/powershell/Language/Scripting/ConstrainedLanguageMode.Tests.ps1 new file mode 100644 index 0000000000..33ea8b1186 --- /dev/null +++ b/test/powershell/Language/Scripting/ConstrainedLanguageMode.Tests.ps1 @@ -0,0 +1,42 @@ + +Describe "Test constrained language mode" -Tags "CI" { + It "dynamic invocation on non-PowerShell thread should work" { + $refAssemblies = if ($IsCoreCLR) { + "Microsoft.CSharp","System.Dynamic.Runtime","System.Management.Automation" + } + else { + "Microsoft.CSharp" + } + + $t,$null = Add-Type -ReferencedAssemblies $refAssemblies -WarningAction Ignore -PassThru @" + public class BinderBug$(Get-Date -Format FileDateTime) + { + public static object Test(System.Management.Automation.PSObject psobj) + { + // Invoke a method through PSObject, but with dynamic, so we get PowerShell's dynamic site binder involved + // And we do this on a different thread so there is no ExecutionContext/runspace to check the language mode + // The actual method called doesn't really matter. + + return System.Threading.Tasks.Task.Run(() => ((dynamic)psobj).AddCommand("Get-Command")).Result; + } + } +"@ + + $o = [powershell]::Create() + $t::Test($o) | Should Be $o + + try + { + # Set language mode to ConstrainedLanguage on a different runspace (so it doesn't affect this runspace) + $ps = [powershell]::Create() + $null = $ps.AddScript('$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"') + $null = $ps.Invoke() + + $t::Test($o) | Should Be $o + } + finally + { + $ps.Dispose() + } + } +} \ No newline at end of file