Fix NullReferenceException in binder (#2591)

There was a NullReferenceException after turning on constrained language mode
if creating a binding on a thread with no runspace.

The fix is to assume if there is no runspace that we are not in constrained language
mode, and also generate a binding restriction that assumes the same.
This commit is contained in:
Jason Shirk
2016-11-03 14:15:58 -07:00
committed by Dongbo Wang
parent 46ff660271
commit c8c55b7ce7
2 changed files with 67 additions and 26 deletions
@@ -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)
{
@@ -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()
}
}
}