7767 lines
374 KiB
C#
7767 lines
374 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Data;
|
|
using System.Dynamic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Xml;
|
|
|
|
namespace System.Management.Automation.Language
|
|
{
|
|
// Item1 - member name
|
|
// Item2 - class containing dynamic site (for protected/private member access)
|
|
// Item3 - static (true) or instance (false)
|
|
// Item4 - enumerating (true) or not (false)
|
|
using PSGetMemberBinderKeyType = Tuple<string, Type, bool, bool>;
|
|
|
|
// Item1 - member name
|
|
// Item2 - class containing dynamic site (for protected/private member access)
|
|
// Item3 - enumerating (true) or not (false)
|
|
using PSSetMemberBinderKeyType = Tuple<string, Type, bool>;
|
|
|
|
// Item1 - member name
|
|
// Item2 - callinfo (# of args and (not used) named arguments)
|
|
// Item3 - property setter (true) or not (false)
|
|
// Item4 - enumerating (true) or not (false)
|
|
// Item5 - invocation constraints (casts used in the invocation expression used to guide overload resolution)
|
|
// Item6 - static (true) or instance (false)
|
|
// Item7 - class containing dynamic site (for protected/private member access)
|
|
using PSInvokeMemberBinderKeyType = Tuple<string, CallInfo, bool, bool, PSMethodInvocationConstraints, bool, Type>;
|
|
|
|
// Item1 - callinfo (# of args and (not used) named arguments)
|
|
// Item2 - invocation constraints (casts used in the invocation expression used to guide overload resolution)
|
|
// Item3 - property setter (true) or not (false)
|
|
// Item4 - static (true) or instance (false)
|
|
// Item5 - class containing dynamic site (for protected/private member access)
|
|
using PSInvokeDynamicMemberBinderKeyType = Tuple<CallInfo, PSMethodInvocationConstraints, bool, bool, Type>;
|
|
|
|
// Item1 - class containing dynamic site (for protected/private member access)
|
|
// Item2 - static (true) or instance (false)
|
|
using PSGetOrSetDynamicMemberBinderKeyType = Tuple<Type, bool>;
|
|
|
|
/// <summary>
|
|
/// Extension methods for DynamicMetaObject. Some of these extensions help handle PSObject, both in terms of
|
|
/// getting the type or value from a DynamicMetaObject, but also help to generate binding restrictions that
|
|
/// account for values that are optionally wrapped in PSObject.
|
|
/// </summary>
|
|
internal static class DynamicMetaObjectExtensions
|
|
{
|
|
internal static readonly DynamicMetaObject FakeError
|
|
= new DynamicMetaObject(ExpressionCache.NullConstant, BindingRestrictions.Empty);
|
|
|
|
internal static DynamicMetaObject WriteToDebugLog(this DynamicMetaObject obj, DynamicMetaObjectBinder binder)
|
|
{
|
|
#if ENABLE_BINDER_DEBUG_LOGGING
|
|
if (obj != FakeError)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("Binder: {0}\r\n Restrictions: {2}\r\n Target: {1}",
|
|
binder.ToString(),
|
|
obj.Expression.ToDebugString(),
|
|
obj.Restrictions.ToDebugString());
|
|
}
|
|
#endif
|
|
return obj;
|
|
}
|
|
|
|
internal static BindingRestrictions GetSimpleTypeRestriction(this DynamicMetaObject obj)
|
|
{
|
|
if (obj.Value == null)
|
|
{
|
|
return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);
|
|
}
|
|
|
|
return BindingRestrictions.GetTypeRestriction(obj.Expression, obj.Value.GetType());
|
|
}
|
|
|
|
internal static BindingRestrictions PSGetMethodArgumentRestriction(this DynamicMetaObject obj)
|
|
{
|
|
var baseValue = PSObject.Base(obj.Value);
|
|
if (baseValue != null && baseValue.GetType() == typeof(object[]))
|
|
{
|
|
var effectiveArgType = Adapter.EffectiveArgumentType(obj.Value);
|
|
var methodInfo = effectiveArgType != typeof(object[])
|
|
? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogenousArray.MakeGenericMethod(effectiveArgType.GetElementType())
|
|
: CachedReflectionInfo.PSInvokeMemberBinder_IsHeterogeneousArray;
|
|
|
|
BindingRestrictions restrictions;
|
|
Expression test;
|
|
if (obj.Value != baseValue)
|
|
{
|
|
// Need PSObject...
|
|
restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, typeof(PSObject));
|
|
|
|
var temp = Expression.Variable(typeof(object[]));
|
|
test = Expression.Block(
|
|
new[] { temp },
|
|
Expression.Assign(temp, Expression.TypeAs(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression), typeof(object[]))),
|
|
Expression.AndAlso(
|
|
Expression.NotEqual(temp, ExpressionCache.NullObjectArray),
|
|
Expression.Call(methodInfo, temp)));
|
|
}
|
|
else
|
|
{
|
|
restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, typeof(object[]));
|
|
var arrayExpr = obj.Expression.Cast(typeof(object[]));
|
|
test = Expression.Call(methodInfo, arrayExpr);
|
|
}
|
|
|
|
return restrictions.Merge(BindingRestrictions.GetExpressionRestriction(test));
|
|
}
|
|
|
|
return obj.PSGetTypeRestriction();
|
|
}
|
|
|
|
internal static BindingRestrictions PSGetStaticMemberRestriction(this DynamicMetaObject obj)
|
|
{
|
|
if (obj.Restrictions != BindingRestrictions.Empty)
|
|
{
|
|
return obj.Restrictions;
|
|
}
|
|
|
|
if (obj.Value == null)
|
|
{
|
|
return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);
|
|
}
|
|
|
|
var baseValue = PSObject.Base(obj.Value);
|
|
if (baseValue == null)
|
|
{
|
|
Diagnostics.Assert(obj.Value == AutomationNull.Value, "PSObject.Base should only return null for AutomationNull.Value");
|
|
return BindingRestrictions.GetExpressionRestriction(Expression.Equal(obj.Expression, Expression.Constant(AutomationNull.Value)));
|
|
}
|
|
|
|
BindingRestrictions restrictions;
|
|
|
|
if (baseValue is Type)
|
|
{
|
|
if (obj.Value == baseValue)
|
|
{
|
|
// newObj == oldObj (if not wrapped in PSObject) or
|
|
restrictions = BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);
|
|
}
|
|
else
|
|
{
|
|
// newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj) == oldObj
|
|
restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);
|
|
restrictions = restrictions.Merge(
|
|
BindingRestrictions.GetInstanceRestriction(
|
|
Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),
|
|
baseValue));
|
|
}
|
|
}
|
|
else if (obj.Value != baseValue)
|
|
{
|
|
// Binding restriction will look like:
|
|
// newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj).GetType() == typeof(oldType)
|
|
|
|
restrictions = BindingRestrictions.GetTypeRestriction(
|
|
Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),
|
|
baseValue.GetType());
|
|
}
|
|
else
|
|
{
|
|
restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);
|
|
}
|
|
|
|
return restrictions;
|
|
}
|
|
|
|
internal static BindingRestrictions PSGetTypeRestriction(this DynamicMetaObject obj)
|
|
{
|
|
if (obj.Restrictions != BindingRestrictions.Empty)
|
|
{
|
|
return obj.Restrictions;
|
|
}
|
|
|
|
if (obj.Value == null)
|
|
{
|
|
return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);
|
|
}
|
|
|
|
var baseValue = PSObject.Base(obj.Value);
|
|
if (baseValue == null)
|
|
{
|
|
Diagnostics.Assert(obj.Value == AutomationNull.Value, "PSObject.Base should only return null for AutomationNull.Value");
|
|
return BindingRestrictions.GetExpressionRestriction(Expression.Equal(obj.Expression, Expression.Constant(AutomationNull.Value)));
|
|
}
|
|
|
|
// The default restriction is a simple type test. We use this type test even if the object is a PSObject,
|
|
// this way we can avoid calling PSObject.Base in all restriction checks.
|
|
var restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);
|
|
|
|
if (obj.Value != baseValue)
|
|
{
|
|
// Binding restriction will look like:
|
|
// newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj).GetType() == typeof(oldType)
|
|
|
|
restrictions = restrictions.Merge(
|
|
BindingRestrictions.GetTypeRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),
|
|
baseValue.GetType()));
|
|
}
|
|
else if (baseValue is PSObject)
|
|
{
|
|
// We have an empty custom object. The restrictions must check this explicitly, otherwise we have
|
|
// a simple type test on PSObject, which obviously tests true for many objects.
|
|
// So we end up with:
|
|
// newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj) == newObj
|
|
|
|
restrictions = restrictions.Merge(
|
|
BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression), obj.Expression)));
|
|
}
|
|
|
|
return restrictions;
|
|
}
|
|
|
|
internal static BindingRestrictions CombineRestrictions(this DynamicMetaObject target, params DynamicMetaObject[] args)
|
|
{
|
|
var restrictions = target.Restrictions == BindingRestrictions.Empty ? target.PSGetTypeRestriction() : target.Restrictions;
|
|
|
|
for (int index = 0; index < args.Length; index++)
|
|
{
|
|
var r = args[index];
|
|
restrictions =
|
|
restrictions.Merge(r.Restrictions == BindingRestrictions.Empty
|
|
? r.PSGetTypeRestriction()
|
|
: r.Restrictions);
|
|
}
|
|
|
|
return restrictions;
|
|
}
|
|
|
|
internal static Expression CastOrConvertMethodArgument(this DynamicMetaObject target,
|
|
Type parameterType,
|
|
string parameterName,
|
|
string methodName,
|
|
bool allowCastingToByRefLikeType,
|
|
List<ParameterExpression> temps,
|
|
List<Expression> initTemps)
|
|
{
|
|
if (target.Value == AutomationNull.Value)
|
|
{
|
|
return Expression.Constant(null, parameterType);
|
|
}
|
|
|
|
var argType = target.LimitType;
|
|
if (parameterType == typeof(object) && argType == typeof(PSObject))
|
|
{
|
|
return Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression.Cast(typeof(PSObject)));
|
|
}
|
|
|
|
// If the conversion can't fail, skip wrapping the conversion in try/catch so we generate less code.
|
|
if (parameterType.IsAssignableFrom(argType))
|
|
{
|
|
return target.Expression.Cast(parameterType);
|
|
}
|
|
|
|
ConversionRank? rank = null;
|
|
if (parameterType.IsByRefLike && allowCastingToByRefLikeType)
|
|
{
|
|
var conversionResult = PSConvertBinder.ConvertToByRefLikeTypeViaCasting(target, parameterType);
|
|
if (conversionResult != null)
|
|
{
|
|
return conversionResult;
|
|
}
|
|
|
|
rank = ConversionRank.None;
|
|
}
|
|
|
|
var exceptionParam = Expression.Variable(typeof(Exception));
|
|
var targetTemp = Expression.Variable(target.Expression.Type);
|
|
bool debase = false;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion are covered by the method resolution algorithm
|
|
// (which ignores method arguments with disallowed types)
|
|
var conversion = rank == ConversionRank.None
|
|
? LanguagePrimitives.NoConversion
|
|
: LanguagePrimitives.FigureConversion(target.Value, parameterType, out debase);
|
|
var invokeConverter = PSConvertBinder.InvokeConverter(conversion, targetTemp, parameterType, debase, ExpressionCache.InvariantCulture);
|
|
var expr =
|
|
Expression.Block(new[] { targetTemp },
|
|
Expression.TryCatch(
|
|
Expression.Block(
|
|
Expression.Assign(targetTemp, target.Expression),
|
|
invokeConverter),
|
|
Expression.Catch(exceptionParam,
|
|
Expression.Block(
|
|
Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToArgumentConversionException,
|
|
exceptionParam,
|
|
Expression.Constant(parameterName),
|
|
targetTemp.Cast(typeof(object)),
|
|
Expression.Constant(methodName),
|
|
Expression.Constant(parameterType, typeof(Type))),
|
|
Expression.Default(invokeConverter.Type)))));
|
|
var tmp = Expression.Variable(expr.Type);
|
|
temps.Add(tmp);
|
|
initTemps.Add(Expression.Assign(tmp, expr));
|
|
return tmp;
|
|
}
|
|
|
|
internal static Expression CastOrConvert(this DynamicMetaObject target, Type type)
|
|
{
|
|
if (target.LimitType == type)
|
|
{
|
|
return target.Expression.Cast(type);
|
|
}
|
|
|
|
bool debase;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion are done by:
|
|
// Switch statements (always to Object), method invocation (protected by InvokeMember binder),
|
|
// and hard-coded casts to integral types.
|
|
var conversion = LanguagePrimitives.FigureConversion(target.Value, type, out debase);
|
|
return PSConvertBinder.InvokeConverter(conversion, target.Expression, type, debase, ExpressionCache.InvariantCulture);
|
|
}
|
|
|
|
internal static DynamicMetaObject ThrowRuntimeError(this DynamicMetaObject target, DynamicMetaObject[] args,
|
|
BindingRestrictions moreTests, string errorID,
|
|
string resourceString, params Expression[] exceptionArgs)
|
|
{
|
|
return new DynamicMetaObject(Compiler.ThrowRuntimeError(errorID, resourceString, exceptionArgs),
|
|
target.CombineRestrictions(args).Merge(moreTests));
|
|
}
|
|
|
|
internal static DynamicMetaObject ThrowRuntimeError(this DynamicMetaObject target, BindingRestrictions bindingRestrictions,
|
|
string errorID, string resourceString, params Expression[] exceptionArgs)
|
|
{
|
|
return new DynamicMetaObject(Compiler.ThrowRuntimeError(errorID, resourceString, exceptionArgs),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
#if ENABLE_BINDER_DEBUG_LOGGING
|
|
internal static string ToDebugString(this BindingRestrictions restrictions)
|
|
{
|
|
return restrictions.ToExpression().ToDebugString();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
internal static class DynamicMetaObjectBinderExtensions
|
|
{
|
|
internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject target, bool targetIsComObject = false)
|
|
{
|
|
Diagnostics.Assert(target.Value is PSObject, "target must be a psobject");
|
|
|
|
BindingRestrictions restrictions = BindingRestrictions.Empty;
|
|
Expression expr = ProcessOnePSObject(target, ref restrictions, argIsComObject: targetIsComObject);
|
|
return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, expr), restrictions);
|
|
}
|
|
|
|
internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject target, DynamicMetaObject arg, bool targetIsComObject = false)
|
|
{
|
|
Diagnostics.Assert(target.Value is PSObject || arg.Value is PSObject, "At least one arg must be a psobject");
|
|
|
|
BindingRestrictions restrictions = BindingRestrictions.Empty;
|
|
Expression expr1 = ProcessOnePSObject(target, ref restrictions, argIsComObject: targetIsComObject);
|
|
Expression expr2 = ProcessOnePSObject(arg, ref restrictions, argIsComObject: false);
|
|
return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, expr1, expr2), restrictions);
|
|
}
|
|
|
|
internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject[] args, bool targetIsComObject = false)
|
|
{
|
|
Diagnostics.Assert(args != null && args.Length > 0, "args should not be null or empty");
|
|
Diagnostics.Assert(args.Any(mo => mo.Value is PSObject), "At least one arg must be a psobject");
|
|
|
|
Expression[] exprs = new Expression[args.Length];
|
|
BindingRestrictions restrictions = BindingRestrictions.Empty;
|
|
|
|
// Target maps to arg[0] of the binder.
|
|
exprs[0] = ProcessOnePSObject(args[0], ref restrictions, targetIsComObject);
|
|
for (int i = 1; i < args.Length; i++)
|
|
{
|
|
exprs[i] = ProcessOnePSObject(args[i], ref restrictions, argIsComObject: false);
|
|
}
|
|
|
|
return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, exprs), restrictions);
|
|
}
|
|
|
|
private static Expression ProcessOnePSObject(DynamicMetaObject arg, ref BindingRestrictions restrictions, bool argIsComObject = false)
|
|
{
|
|
Expression expr = null;
|
|
object baseValue = PSObject.Base(arg.Value);
|
|
if (baseValue != arg.Value)
|
|
{
|
|
expr = Expression.Call(CachedReflectionInfo.PSObject_Base, arg.Expression.Cast(typeof(object)));
|
|
|
|
if (argIsComObject)
|
|
{
|
|
// The 'base' is a COM object, so bake that in the rule.
|
|
restrictions = restrictions
|
|
.Merge(arg.GetSimpleTypeRestriction())
|
|
.Merge(BindingRestrictions.GetExpressionRestriction(Expression.Call(CachedReflectionInfo.Utils_IsComObject, expr)));
|
|
}
|
|
else
|
|
{
|
|
// Use a more general condition for the rule: 'arg' is a PSObject and 'base != arg'.
|
|
restrictions = restrictions
|
|
.Merge(arg.GetSimpleTypeRestriction())
|
|
.Merge(BindingRestrictions.GetExpressionRestriction(Expression.NotEqual(expr, arg.Expression)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
expr = arg.Expression;
|
|
restrictions = restrictions.Merge(arg.PSGetTypeRestriction());
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
internal static DynamicMetaObject UpdateComRestrictionsForPsObject(this DynamicMetaObject binder, DynamicMetaObject[] args)
|
|
{
|
|
// Add a restriction that prevents PSObject arguments (so that they get based)
|
|
BindingRestrictions newRestrictions = binder.Restrictions;
|
|
newRestrictions = args.Aggregate(newRestrictions, (current, arg) =>
|
|
{
|
|
if (arg.LimitType.IsValueType)
|
|
{
|
|
return current.Merge(arg.GetSimpleTypeRestriction());
|
|
}
|
|
else
|
|
{
|
|
return current.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(
|
|
Expression.Call(CachedReflectionInfo.PSObject_Base, arg.Expression),
|
|
arg.Expression)));
|
|
}
|
|
});
|
|
|
|
return new DynamicMetaObject(binder.Expression, newRestrictions);
|
|
}
|
|
}
|
|
|
|
internal static class BinderUtils
|
|
{
|
|
internal static BindingRestrictions GetVersionCheck(DynamicMetaObjectBinder binder, int expectedVersionNumber)
|
|
{
|
|
return BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(Expression.Field(Expression.Constant(binder), "_version"),
|
|
ExpressionCache.Constant(expectedVersionNumber)));
|
|
}
|
|
|
|
internal static BindingRestrictions GetLanguageModeCheckIfHasEverUsedConstrainedLanguage()
|
|
{
|
|
// Also add a language mode check to detect toggling between language modes
|
|
if (ExecutionContext.HasEverUsedConstrainedLanguage)
|
|
{
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
|
|
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 BindingRestrictions.Empty;
|
|
}
|
|
|
|
internal static BindingRestrictions GetOptionalVersionAndLanguageCheckForType(DynamicMetaObjectBinder binder, Type targetType, int expectedVersionNumber)
|
|
{
|
|
BindingRestrictions additionalBindingRestrictions = BindingRestrictions.Empty;
|
|
|
|
// If this uses a potentially unsafe type, we also need a version check
|
|
if (!CoreTypes.Contains(targetType))
|
|
{
|
|
if (expectedVersionNumber != -1)
|
|
{
|
|
additionalBindingRestrictions = additionalBindingRestrictions.Merge(BinderUtils.GetVersionCheck(binder, expectedVersionNumber));
|
|
}
|
|
|
|
additionalBindingRestrictions = additionalBindingRestrictions.Merge(GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
}
|
|
|
|
return additionalBindingRestrictions;
|
|
}
|
|
}
|
|
|
|
#region PowerShell non-standard language binders
|
|
|
|
/// <summary>
|
|
/// Some classes that implement IEnumerable are not considered as enumerable from the perspective of pipelines,
|
|
/// this binder implements those semantics.
|
|
///
|
|
/// The standard interop ConvertBinder is used to allow third party dynamic objects to get the first chance
|
|
/// at the conversion in case they do support enumeration, but do not implement IEnumerable directly.
|
|
/// </summary>
|
|
internal sealed class PSEnumerableBinder : ConvertBinder
|
|
{
|
|
private static readonly PSEnumerableBinder s_binder = new PSEnumerableBinder();
|
|
|
|
internal static PSEnumerableBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSEnumerableBinder()
|
|
: base(typeof(IEnumerator), false)
|
|
{
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(PSObjectStringRule));
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(ArrayRule));
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(StringRule));
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(NotEnumerableRule));
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(PSObjectNotEnumerableRule));
|
|
CacheTarget((Func<CallSite, object, IEnumerator>)(AutomationNullRule));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "ToEnumerable";
|
|
}
|
|
|
|
internal static BindingRestrictions GetRestrictions(DynamicMetaObject target)
|
|
{
|
|
return (target.Value is PSObject)
|
|
? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType())
|
|
: target.PSGetTypeRestriction();
|
|
}
|
|
|
|
private DynamicMetaObject NullResult(DynamicMetaObject target)
|
|
{
|
|
// The object is not enumerable from PowerShell's perspective. Rather than raise an exception, we let the
|
|
// caller check for null and take the appropriate action.
|
|
return new DynamicMetaObject(
|
|
MaybeDebase(this, static e => ExpressionCache.NullEnumerator, target),
|
|
GetRestrictions(target));
|
|
}
|
|
|
|
internal static Expression MaybeDebase(DynamicMetaObjectBinder binder, Func<Expression, Expression> generator, DynamicMetaObject target)
|
|
{
|
|
if (target.Value is not PSObject)
|
|
{
|
|
return generator(target.Expression);
|
|
}
|
|
|
|
object targetValue = PSObject.Base(target.Value);
|
|
|
|
var tmp = Expression.Parameter(typeof(object), "value");
|
|
return Expression.Block(
|
|
new ParameterExpression[] { tmp },
|
|
Expression.Assign(tmp, Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression)),
|
|
Expression.Condition(
|
|
targetValue == null ?
|
|
(Expression)Expression.AndAlso(Expression.Equal(tmp, ExpressionCache.NullConstant),
|
|
Expression.Not(Expression.Equal(target.Expression, ExpressionCache.AutomationNullConstant)))
|
|
: Expression.TypeEqual(tmp, targetValue.GetType()),
|
|
generator(tmp), binder.GetUpdateExpression(binder.ReturnType)));
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value == AutomationNull.Value)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(Expression.Constant(Array.Empty<object>()), typeof(Array).GetMethod("GetEnumerator")),
|
|
BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this);
|
|
}
|
|
|
|
var targetValue = PSObject.Base(target.Value);
|
|
|
|
if (targetValue == null || targetValue is string || targetValue is PSObject)
|
|
{
|
|
return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (targetValue.GetType().IsArray)
|
|
{
|
|
return (new DynamicMetaObject(
|
|
MaybeDebase(this, static e => Expression.Call(Expression.Convert(e, typeof(Array)), typeof(Array).GetMethod("GetEnumerator")),
|
|
target),
|
|
GetRestrictions(target))).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (targetValue is IDictionary || targetValue is XmlNode)
|
|
{
|
|
return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (targetValue is DataTable)
|
|
{
|
|
// Generate:
|
|
//
|
|
// DataRowCollection rows;
|
|
// DataTable table = (DataTable)obj;
|
|
// if ((rows = table.Rows) != null)
|
|
// return rows.GetEnumerator();
|
|
// else
|
|
// return null;
|
|
|
|
return (new DynamicMetaObject(
|
|
MaybeDebase(this, e =>
|
|
{
|
|
var table = Expression.Parameter(typeof(DataTable), "table");
|
|
var rows = Expression.Parameter(typeof(DataRowCollection), "rows");
|
|
return Expression.Block(new ParameterExpression[] { table, rows },
|
|
Expression.Assign(table, e.Cast(typeof(DataTable))),
|
|
Expression.Condition(
|
|
Expression.NotEqual(Expression.Assign(rows, Expression.Property(table, "Rows")), ExpressionCache.NullConstant),
|
|
Expression.Call(rows, typeof(DataRowCollection).GetMethod("GetEnumerator")),
|
|
ExpressionCache.NullEnumerator));
|
|
},
|
|
target),
|
|
GetRestrictions(target))).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (Marshal.IsComObject(targetValue))
|
|
{
|
|
// Pretend that all com objects are enumerable, even if they aren't. We do this because it's technically impossible
|
|
// to know if a com object is enumerable without just trying to cast it to IEnumerable. We could generate a rule like:
|
|
//
|
|
// if (IsComObject(obj)) { return obj as IEnumerable; } else { UpdateSite; }
|
|
//
|
|
// But code that calls PSEnumerableBinder.IsEnumerable and generate code based on the true/false result of that
|
|
// function wouldn't work properly. Instead, we'll fix things up after the binding decisions are made, see
|
|
// EnumerableOps.NonEnumerableObjectEnumerator for more comments on how this works.
|
|
|
|
var bindingRestrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.Utils_IsComObject,
|
|
Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression)));
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_GetCOMEnumerator, target.Expression), bindingRestrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var enumerable = targetValue as IEnumerable;
|
|
if (enumerable != null)
|
|
{
|
|
// Normally it's safe to just call IEnumerable.GetEnumerator, but in some rare cases, the
|
|
// non-generic implementation throws or returns null, so we'll just avoid that problem and
|
|
// call the generic version if it exists.
|
|
|
|
foreach (var i in targetValue.GetType().GetInterfaces())
|
|
{
|
|
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
|
{
|
|
return (new DynamicMetaObject(
|
|
MaybeDebase(this, e => Expression.Call(
|
|
CachedReflectionInfo.EnumerableOps_GetGenericEnumerator.MakeGenericMethod(i.GetGenericArguments()[0]), Expression.Convert(e, i)),
|
|
target),
|
|
GetRestrictions(target))).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
return (new DynamicMetaObject(
|
|
MaybeDebase(this, static e => Expression.Call(CachedReflectionInfo.EnumerableOps_GetEnumerator, Expression.Convert(e, typeof(IEnumerable))),
|
|
target),
|
|
GetRestrictions(target))).WriteToDebugLog(this);
|
|
}
|
|
|
|
var enumerator = targetValue as IEnumerator;
|
|
if (enumerator != null)
|
|
{
|
|
return (new DynamicMetaObject(
|
|
MaybeDebase(this, static e => e.Cast(typeof(IEnumerator)), target),
|
|
GetRestrictions(target))).WriteToDebugLog(this);
|
|
}
|
|
|
|
return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the statically known type is potentially enumerable. We can avoid some dynamic sites if we know the
|
|
/// type is never enumerable.
|
|
/// </summary>
|
|
internal static bool IsStaticTypePossiblyEnumerable(Type type)
|
|
{
|
|
if (type == typeof(object) || type == typeof(PSObject) || type.IsArray)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (type == typeof(string) || typeof(IDictionary).IsAssignableFrom(type) || typeof(XmlNode).IsAssignableFrom(type))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (type.IsSealed && !typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IEnumerator).IsAssignableFrom(type))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Binders normally cannot return null, but we want a way to detect if something is enumerable,
|
|
// so we return null if the target is not enumerable.
|
|
internal static DynamicMetaObject IsEnumerable(DynamicMetaObject target)
|
|
{
|
|
var binder = PSEnumerableBinder.Get();
|
|
var result = binder.FallbackConvert(target, DynamicMetaObjectExtensions.FakeError);
|
|
return (result == DynamicMetaObjectExtensions.FakeError) ? null : result;
|
|
}
|
|
|
|
private static IEnumerator AutomationNullRule(CallSite site, object obj)
|
|
{
|
|
return obj == AutomationNull.Value
|
|
? Array.Empty<object>().GetEnumerator()
|
|
: ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static IEnumerator NotEnumerableRule(CallSite site, object obj)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (obj is not PSObject
|
|
&& obj is not IEnumerable
|
|
&& obj is not IEnumerator
|
|
&& obj is not DataTable
|
|
&& !Marshal.IsComObject(obj))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static IEnumerator PSObjectNotEnumerableRule(CallSite site, object obj)
|
|
{
|
|
var psobj = obj as PSObject;
|
|
return psobj != null && obj != AutomationNull.Value
|
|
? NotEnumerableRule(site, PSObject.Base(obj))
|
|
: ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static IEnumerator ArrayRule(CallSite site, object obj)
|
|
{
|
|
var array = obj as Array;
|
|
if (array != null) return array.GetEnumerator();
|
|
return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static IEnumerator StringRule(CallSite site, object obj)
|
|
{
|
|
return obj is string ? null : ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static IEnumerator PSObjectStringRule(CallSite site, object obj)
|
|
{
|
|
var psobj = obj as PSObject;
|
|
if (psobj != null && PSObject.Base(psobj) is string) return null;
|
|
return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This binder is used for the @() operator.
|
|
/// </summary>
|
|
internal sealed class PSToObjectArrayBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSToObjectArrayBinder s_binder = new PSToObjectArrayBinder();
|
|
|
|
internal static PSToObjectArrayBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSToObjectArrayBinder()
|
|
{
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "ToObjectArray";
|
|
}
|
|
|
|
public override Type ReturnType { get { return typeof(object[]); } }
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target, args);
|
|
}
|
|
|
|
if (target.Value == AutomationNull.Value)
|
|
{
|
|
return new DynamicMetaObject(Expression.Constant(Array.Empty<object>()),
|
|
BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this);
|
|
}
|
|
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.NewArrayInit(typeof(object), target.Expression.Cast(typeof(object))),
|
|
target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
|
|
var value = PSObject.Base(target.Value);
|
|
if (value is List<object>)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(PSEnumerableBinder.MaybeDebase(this, static e => e.Cast(typeof(List<object>)), target), CachedReflectionInfo.ObjectList_ToArray),
|
|
PSEnumerableBinder.GetRestrictions(target)).WriteToDebugLog(this);
|
|
}
|
|
|
|
// It's enumerable, but not an List<object>. Call EnumerableOps.ToArray
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_ToArray, enumerable.Expression),
|
|
target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
internal sealed class PSPipeWriterBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSPipeWriterBinder s_binder = new PSPipeWriterBinder();
|
|
|
|
internal static PSPipeWriterBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSPipeWriterBinder()
|
|
{
|
|
CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)StringRule);
|
|
CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)AutomationNullRule);
|
|
CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)BoolRule);
|
|
CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)IntRule);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "PipelineWriter";
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
// args[0] is the pipe
|
|
// args[1] is the execution context, only used if we're enumerating
|
|
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target, args);
|
|
}
|
|
|
|
if (target.Value == AutomationNull.Value)
|
|
{
|
|
return (new DynamicMetaObject(
|
|
Expression.Block(typeof(void), Expression.Call(CachedReflectionInfo.PipelineOps_Nop)),
|
|
BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value))).WriteToDebugLog(this);
|
|
}
|
|
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null)
|
|
{
|
|
var bindingResult = PSVariableAssignmentBinder.Get().Bind(target, Array.Empty<DynamicMetaObject>());
|
|
var restrictions = target.LimitType.IsValueType
|
|
? bindingResult.Restrictions
|
|
: target.PSGetTypeRestriction();
|
|
return (new DynamicMetaObject(
|
|
Expression.Call(args[0].Expression,
|
|
CachedReflectionInfo.Pipe_Add,
|
|
bindingResult.Expression.Cast(typeof(object))),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
bool needsToDispose = PSObject.Base(target.Value) is not IEnumerator;
|
|
return (new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_WriteEnumerableToPipe,
|
|
enumerable.Expression,
|
|
args[0].Expression,
|
|
args[1].Expression,
|
|
ExpressionCache.Constant(needsToDispose)),
|
|
enumerable.Restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
private static void BoolRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)
|
|
{
|
|
if (obj is bool) { pipe.Add(obj); }
|
|
else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }
|
|
}
|
|
|
|
private static void IntRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)
|
|
{
|
|
if (obj is int) { pipe.Add(obj); }
|
|
else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }
|
|
}
|
|
|
|
private static void StringRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)
|
|
{
|
|
if (obj is string) { pipe.Add(obj); }
|
|
else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }
|
|
}
|
|
|
|
private static void AutomationNullRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)
|
|
{
|
|
if (obj != AutomationNull.Value) { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This binder creates the collection we use to do multi-assignments, e.g.:
|
|
/// $x,$y = $z
|
|
/// $x,$y,$z = 1,2,3,4,5
|
|
/// The target in this binder is the RHS, the result expression is an IList where the Count matches the
|
|
/// number of values assigned (_elements) on the left hand side of the assign.
|
|
/// </summary>
|
|
internal sealed class PSArrayAssignmentRHSBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly List<PSArrayAssignmentRHSBinder> s_binders = new List<PSArrayAssignmentRHSBinder>();
|
|
private readonly int _elements;
|
|
|
|
internal static PSArrayAssignmentRHSBinder Get(int i)
|
|
{
|
|
lock (s_binders)
|
|
{
|
|
while (s_binders.Count <= i)
|
|
{
|
|
s_binders.Add(null);
|
|
}
|
|
|
|
return s_binders[i] ?? (s_binders[i] = new PSArrayAssignmentRHSBinder(i));
|
|
}
|
|
}
|
|
|
|
private PSArrayAssignmentRHSBinder(int elements)
|
|
{
|
|
_elements = elements;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "MultiAssignRHSBinder {0}", _elements);
|
|
}
|
|
|
|
public override Type ReturnType { get { return typeof(IList); } }
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
Diagnostics.Assert(args.Length == 0, "binder doesn't expect any arguments");
|
|
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
var iList = target.Value as IList;
|
|
if (iList != null)
|
|
{
|
|
// 3 possibilities - too few, exact, or too many elements.
|
|
|
|
var getListCountExpr = Expression.Property(target.Expression.Cast(typeof(ICollection)), CachedReflectionInfo.ICollection_Count);
|
|
|
|
var restrictions = target.PSGetTypeRestriction().Merge(
|
|
BindingRestrictions.GetExpressionRestriction(Expression.Equal(getListCountExpr,
|
|
ExpressionCache.Constant(iList.Count))));
|
|
|
|
if (iList.Count == _elements)
|
|
{
|
|
// Exact, we can use the value as is.
|
|
return new DynamicMetaObject(target.Expression.Cast(typeof(IList)), restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
int i;
|
|
Expression[] newArrayElements = new Expression[_elements];
|
|
var temp = Expression.Variable(typeof(IList));
|
|
if (iList.Count < _elements)
|
|
{
|
|
// Too few, create an array with the correct number assigned, fill in null for the extras
|
|
|
|
for (i = 0; i < iList.Count; ++i)
|
|
{
|
|
newArrayElements[i] =
|
|
Expression.Call(temp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i));
|
|
}
|
|
|
|
for (; i < _elements; ++i)
|
|
{
|
|
newArrayElements[i] = ExpressionCache.NullConstant;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Too many, create an array with the correct number assigned, and the last element contains
|
|
// all the extras.
|
|
for (i = 0; i < _elements - 1; ++i)
|
|
{
|
|
newArrayElements[i] =
|
|
Expression.Call(temp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i));
|
|
}
|
|
|
|
newArrayElements[_elements - 1] =
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_GetSlice, temp, ExpressionCache.Constant(_elements - 1)).Cast(typeof(object));
|
|
}
|
|
|
|
return (new DynamicMetaObject(
|
|
Expression.Block(
|
|
new[] { temp },
|
|
Expression.Assign(temp, target.Expression.Cast(typeof(IList))),
|
|
Expression.NewArrayInit(typeof(object), newArrayElements)),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
// We have a single element, the rest must be null.
|
|
return (new DynamicMetaObject(
|
|
Expression.NewArrayInit(typeof(object), Enumerable.Repeat(ExpressionCache.NullConstant, _elements - 1)
|
|
.Prepend(target.Expression.Cast(typeof(object)))),
|
|
target.PSGetTypeRestriction())).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This binder is used to convert objects to string in specific circumstances, including:
|
|
///
|
|
/// * The LHS of a format expression. The arguments (the RHS objects) of the format
|
|
/// expression are not converted to string here, that is defered to string.Format which
|
|
/// may have some custom formatting to apply.
|
|
/// * The objects passed to the format expression as part of an expandable string. In this
|
|
/// case, the format string is generated by the parser, so we know that there is no custom
|
|
/// formatting to consider.
|
|
/// </summary>
|
|
internal sealed class PSToStringBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSToStringBinder s_binder = new PSToStringBinder();
|
|
|
|
internal static PSToStringBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
public override Type ReturnType { get { return typeof(string); } }
|
|
|
|
private PSToStringBinder()
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
// target is the object to convert
|
|
// args[0] is the ExecutionContext, needed to get $OFS, and possibly the type table.
|
|
|
|
if (!target.HasValue || !args[0].HasValue)
|
|
{
|
|
return Defer(target, args).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target, args[0]).WriteToDebugLog(this);
|
|
}
|
|
|
|
var restrictions = target.PSGetTypeRestriction();
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
return (new DynamicMetaObject(
|
|
target.Expression.Cast(typeof(string)),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
// PSObject.ToStringParser will handle everything, but if you want to speed up conversion to string,
|
|
// add special cases here.
|
|
|
|
return (new DynamicMetaObject(
|
|
InvokeToString(args[0].Expression, target.Expression),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
internal static Expression InvokeToString(Expression context, Expression target)
|
|
{
|
|
if (target.Type == typeof(string))
|
|
{
|
|
return target;
|
|
}
|
|
|
|
// PSObject.ToStringParser will handle everything, but if you want to speed up conversion to string,
|
|
// add special cases here.
|
|
|
|
return Expression.Call(CachedReflectionInfo.PSObject_ToStringParser,
|
|
context.Cast(typeof(ExecutionContext)),
|
|
target.Cast(typeof(object)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This binder is used to optimize the conversion of the result.
|
|
/// </summary>
|
|
internal sealed class PSPipelineResultToBoolBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSPipelineResultToBoolBinder s_binder = new PSPipelineResultToBoolBinder();
|
|
|
|
internal static PSPipelineResultToBoolBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSPipelineResultToBoolBinder()
|
|
{
|
|
}
|
|
|
|
public override Type ReturnType { get { return typeof(bool); } }
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
IList pipelineResult = target.Value as IList;
|
|
Diagnostics.Assert(pipelineResult != null, "Pipeline result is always an IList");
|
|
|
|
var ilistExpr = target.Expression;
|
|
if (typeof(IList) != ilistExpr.Type)
|
|
{
|
|
ilistExpr = Expression.Convert(ilistExpr, typeof(IList));
|
|
}
|
|
|
|
var countExpr = Expression.Property(
|
|
Expression.Convert(ilistExpr, typeof(ICollection)), CachedReflectionInfo.ICollection_Count);
|
|
|
|
Expression result;
|
|
BindingRestrictions restrictions;
|
|
switch (pipelineResult.Count)
|
|
{
|
|
case 0:
|
|
result = ExpressionCache.Constant(false);
|
|
restrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(countExpr, ExpressionCache.Constant(0)));
|
|
break;
|
|
case 1:
|
|
result = Expression.Call(ilistExpr,
|
|
CachedReflectionInfo.IList_get_Item,
|
|
ExpressionCache.Constant(0)).Convert(typeof(bool));
|
|
restrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(countExpr, ExpressionCache.Constant(1)));
|
|
break;
|
|
default:
|
|
result = ExpressionCache.Constant(true);
|
|
restrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.GreaterThan(countExpr, ExpressionCache.Constant(1)));
|
|
break;
|
|
}
|
|
|
|
return (new DynamicMetaObject(result, restrictions)).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
internal sealed class PSInvokeDynamicMemberBinder : DynamicMetaObjectBinder
|
|
{
|
|
private sealed class KeyComparer : IEqualityComparer<PSInvokeDynamicMemberBinderKeyType>
|
|
{
|
|
public bool Equals(PSInvokeDynamicMemberBinderKeyType x, PSInvokeDynamicMemberBinderKeyType y)
|
|
{
|
|
return x.Item1.Equals(y.Item1) &&
|
|
((x.Item2 == null) ? y.Item2 == null : x.Item2.Equals(y.Item2)) &&
|
|
x.Item3 == y.Item3 &&
|
|
x.Item4 == y.Item4 &&
|
|
x.Item5 == y.Item5;
|
|
}
|
|
|
|
public int GetHashCode(PSInvokeDynamicMemberBinderKeyType obj)
|
|
{
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
|
|
private static readonly
|
|
Dictionary<PSInvokeDynamicMemberBinderKeyType, PSInvokeDynamicMemberBinder> s_binderCache
|
|
= new Dictionary<PSInvokeDynamicMemberBinderKeyType, PSInvokeDynamicMemberBinder>(new KeyComparer());
|
|
|
|
internal static PSInvokeDynamicMemberBinder Get(CallInfo callInfo, TypeDefinitionAst classScopeAst, bool @static, bool propertySetter, PSMethodInvocationConstraints constraints)
|
|
{
|
|
PSInvokeDynamicMemberBinder result;
|
|
|
|
var classScope = classScopeAst?.Type;
|
|
lock (s_binderCache)
|
|
{
|
|
var key = Tuple.Create(callInfo, constraints, propertySetter, @static, classScope);
|
|
if (!s_binderCache.TryGetValue(key, out result))
|
|
{
|
|
result = new PSInvokeDynamicMemberBinder(callInfo, @static, propertySetter, constraints, classScope);
|
|
s_binderCache.Add(key, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private readonly CallInfo _callInfo;
|
|
private readonly bool _static;
|
|
private readonly bool _propertySetter;
|
|
private readonly PSMethodInvocationConstraints _constraints;
|
|
private readonly Type _classScope;
|
|
|
|
private PSInvokeDynamicMemberBinder(CallInfo callInfo, bool @static, bool propertySetter, PSMethodInvocationConstraints constraints, Type classScope)
|
|
{
|
|
Diagnostics.Assert(callInfo != null, "callers make sure 'callInfo' is not null");
|
|
|
|
_callInfo = callInfo;
|
|
_static = @static;
|
|
_propertySetter = propertySetter;
|
|
_constraints = constraints;
|
|
_classScope = classScope;
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
if (!target.HasValue || !args[0].HasValue)
|
|
{
|
|
return Defer(target, args[0]).WriteToDebugLog(this);
|
|
}
|
|
|
|
var memberNameArg = args[0];
|
|
object memberNameValue = PSObject.Base(memberNameArg.Value);
|
|
|
|
Expression bindingStrExpr;
|
|
var memberName = memberNameValue as string;
|
|
if (memberName != null)
|
|
{
|
|
if (memberNameArg.Value is PSObject)
|
|
{
|
|
bindingStrExpr = Expression.Call(CachedReflectionInfo.PSObject_Base, memberNameArg.Expression).Cast(typeof(string));
|
|
}
|
|
else
|
|
{
|
|
bindingStrExpr = memberNameArg.Expression.Cast(typeof(string));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Context is explicitly null here, we don't want $OFS to influence the property name, or
|
|
// we'd generate code that wouldn't work consistently, depending on the value of $OFS.
|
|
memberName = PSObject.ToStringParser(null, memberNameArg.Value);
|
|
bindingStrExpr = PSToStringBinder.InvokeToString(ExpressionCache.NullConstant, memberNameArg.Expression);
|
|
}
|
|
// Note: Need to create DynamicExpression to support dynamic member invoke, see PSSetDynamicMemberBinder for example
|
|
var result = PSInvokeMemberBinder.Get(memberName, _callInfo, _static, _propertySetter, _constraints, _classScope).FallbackInvokeMember(target, args.Skip(1).ToArray());
|
|
BindingRestrictions restrictions = result.Restrictions;
|
|
restrictions = restrictions.Merge(args[0].PSGetTypeRestriction());
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.String_Equals, Expression.Constant(memberName), bindingStrExpr, ExpressionCache.Ordinal)));
|
|
return (new DynamicMetaObject(result.Expression, restrictions)).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
internal class PSDynamicGetOrSetBinderKeyComparer : IEqualityComparer<PSGetOrSetDynamicMemberBinderKeyType>
|
|
{
|
|
public bool Equals(PSGetOrSetDynamicMemberBinderKeyType x, PSGetOrSetDynamicMemberBinderKeyType y)
|
|
{
|
|
return x.Item1 == y.Item1 && x.Item2 == y.Item2;
|
|
}
|
|
|
|
public int GetHashCode(PSGetOrSetDynamicMemberBinderKeyType obj)
|
|
{
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
|
|
internal sealed class PSGetDynamicMemberBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly Dictionary<PSGetOrSetDynamicMemberBinderKeyType, PSGetDynamicMemberBinder> s_binderCache =
|
|
new Dictionary<PSGetOrSetDynamicMemberBinderKeyType, PSGetDynamicMemberBinder>(new PSDynamicGetOrSetBinderKeyComparer());
|
|
|
|
internal static PSGetDynamicMemberBinder Get(TypeDefinitionAst classScope, bool @static)
|
|
{
|
|
PSGetDynamicMemberBinder binder;
|
|
lock (s_binderCache)
|
|
{
|
|
var type = classScope?.Type;
|
|
var tuple = Tuple.Create(type, @static);
|
|
if (!s_binderCache.TryGetValue(tuple, out binder))
|
|
{
|
|
binder = new PSGetDynamicMemberBinder(type, @static);
|
|
s_binderCache.Add(tuple, binder);
|
|
}
|
|
}
|
|
|
|
return binder;
|
|
}
|
|
|
|
private readonly bool _static;
|
|
private readonly Type _classScope;
|
|
|
|
private PSGetDynamicMemberBinder(Type classScope, bool @static)
|
|
{
|
|
_static = @static;
|
|
_classScope = classScope;
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
Diagnostics.Assert(args.Length == 1, "PSGetDynamicMemberBinder::Bind handles one and only one argument");
|
|
if (!target.HasValue || !args[0].HasValue)
|
|
{
|
|
return Defer(target, args[0]).WriteToDebugLog(this);
|
|
}
|
|
|
|
var memberNameArg = args[0];
|
|
object memberNameValue = PSObject.Base(memberNameArg.Value);
|
|
|
|
Expression bindingStrExpr;
|
|
BindingRestrictions restrictions;
|
|
var memberName = memberNameValue as string;
|
|
if (memberName != null)
|
|
{
|
|
if (memberNameArg.Value is PSObject)
|
|
{
|
|
bindingStrExpr = Expression.Call(CachedReflectionInfo.PSObject_Base, memberNameArg.Expression).Cast(typeof(string));
|
|
}
|
|
else
|
|
{
|
|
bindingStrExpr = memberNameArg.Expression.Cast(typeof(string));
|
|
}
|
|
}
|
|
else if (PSObject.Base(target.Value) is IDictionary)
|
|
{
|
|
// We don't want to convert the member name to a string, we'll just try indexing the dictionary and nothing else.
|
|
restrictions = target.PSGetTypeRestriction();
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Not(Expression.TypeIs(args[0].Expression, typeof(string)))));
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.PSGetDynamicMemberBinder_GetIDictionaryMember,
|
|
PSGetMemberBinder.GetTargetExpr(target, typeof(IDictionary)),
|
|
args[0].Expression.Cast(typeof(object))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
else
|
|
{
|
|
// Context is explicitly null here, we don't want $OFS to influence the property name, or
|
|
// we'd generate code that wouldn't work consistently, depending on the value of $OFS.
|
|
memberName = PSObject.ToStringParser(null, memberNameArg.Value);
|
|
bindingStrExpr = PSToStringBinder.InvokeToString(ExpressionCache.NullConstant, memberNameArg.Expression);
|
|
}
|
|
|
|
var result = DynamicExpression.Dynamic(PSGetMemberBinder.Get(memberName, _classScope, _static), typeof(object), target.Expression);
|
|
restrictions = BindingRestrictions.Empty;
|
|
restrictions = restrictions.Merge(args[0].PSGetTypeRestriction());
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.String_Equals, Expression.Constant(memberName), bindingStrExpr, ExpressionCache.Ordinal)));
|
|
return (new DynamicMetaObject(result, restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
internal static object GetIDictionaryMember(IDictionary hash, object key)
|
|
{
|
|
try
|
|
{
|
|
key = PSObject.Base(key);
|
|
if (hash.Contains(key))
|
|
{
|
|
return hash[key];
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
}
|
|
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
if (context.IsStrictVersion(2))
|
|
{
|
|
// If the member is undefined and we're in strict mode, throw an exception...
|
|
throw new PropertyNotFoundException("PropertyNotFoundStrict", null, ParserStrings.PropertyNotFoundStrict,
|
|
LanguagePrimitives.ConvertTo<string>(key));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal sealed class PSSetDynamicMemberBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly Dictionary<PSGetOrSetDynamicMemberBinderKeyType, PSSetDynamicMemberBinder> s_binderCache =
|
|
new Dictionary<PSGetOrSetDynamicMemberBinderKeyType, PSSetDynamicMemberBinder>(new PSDynamicGetOrSetBinderKeyComparer());
|
|
|
|
internal static PSSetDynamicMemberBinder Get(TypeDefinitionAst classScope, bool @static)
|
|
{
|
|
PSSetDynamicMemberBinder binder;
|
|
lock (s_binderCache)
|
|
{
|
|
var type = classScope?.Type;
|
|
var tuple = Tuple.Create(type, @static);
|
|
if (!s_binderCache.TryGetValue(tuple, out binder))
|
|
{
|
|
binder = new PSSetDynamicMemberBinder(type, @static);
|
|
s_binderCache.Add(tuple, binder);
|
|
}
|
|
}
|
|
|
|
return binder;
|
|
}
|
|
|
|
private readonly bool _static;
|
|
private readonly Type _classScope;
|
|
|
|
private PSSetDynamicMemberBinder(Type classScope, bool @static)
|
|
{
|
|
_static = @static;
|
|
_classScope = classScope;
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
Diagnostics.Assert(args.Length == 2, "PSSetDynamicMemberBinder::Bind handles two and only two arguments");
|
|
if (!target.HasValue || !args[0].HasValue)
|
|
{
|
|
return Defer(target, args[0]).WriteToDebugLog(this);
|
|
}
|
|
|
|
var memberNameArg = args[0];
|
|
object memberNameValue = PSObject.Base(memberNameArg.Value);
|
|
|
|
Expression bindingStrExpr;
|
|
var memberName = memberNameValue as string;
|
|
if (memberName != null)
|
|
{
|
|
if (memberNameArg.Value is PSObject)
|
|
{
|
|
bindingStrExpr = Expression.Call(CachedReflectionInfo.PSObject_Base, memberNameArg.Expression).Cast(typeof(string));
|
|
}
|
|
else
|
|
{
|
|
bindingStrExpr = memberNameArg.Expression.Cast(typeof(string));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Context is explicitly null here, we don't want $OFS to influence the property name, or
|
|
// we'd generate code that wouldn't work consistently, depending on the value of $OFS.
|
|
memberName = PSObject.ToStringParser(null, memberNameArg.Value);
|
|
bindingStrExpr = PSToStringBinder.InvokeToString(ExpressionCache.NullConstant, memberNameArg.Expression);
|
|
}
|
|
|
|
var result = DynamicExpression.Dynamic(PSSetMemberBinder.Get(memberName, _classScope, _static), typeof(object), target.Expression, args[1].Expression);
|
|
var restrictions = BindingRestrictions.Empty;
|
|
// Note: Optimal restriction is test target type is IDictionary or not
|
|
restrictions = restrictions.Merge(args[0].PSGetTypeRestriction());
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.String_Equals, Expression.Constant(memberName), bindingStrExpr, ExpressionCache.Ordinal)));
|
|
|
|
Expression resultExpr;
|
|
if (target.Value is IDictionary)
|
|
{
|
|
// We should first try:
|
|
// $target[$arg[0]] = $arg[1]
|
|
// And if that fails, try arg[0] converted to string, setting a property, etc.
|
|
|
|
var exception = Expression.Variable(typeof(Exception));
|
|
var indexResult = PSSetIndexBinder.Get(1).FallbackSetIndex(target, new[] { args[0] }, args[1]);
|
|
resultExpr = Expression.TryCatch(
|
|
indexResult.Expression,
|
|
Expression.Catch(exception, result));
|
|
}
|
|
else
|
|
{
|
|
resultExpr = result;
|
|
}
|
|
|
|
return (new DynamicMetaObject(resultExpr, restrictions)).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
internal sealed class PSSwitchClauseEvalBinder : DynamicMetaObjectBinder
|
|
{
|
|
// Increase this cache size if we add a new flag to the switch statement that:
|
|
// - Influences evaluation of switch elements
|
|
// - Is commonly used
|
|
private static readonly PSSwitchClauseEvalBinder[] s_binderCache = new PSSwitchClauseEvalBinder[32];
|
|
private readonly SwitchFlags _flags;
|
|
|
|
internal static PSSwitchClauseEvalBinder Get(SwitchFlags flags)
|
|
{
|
|
lock (s_binderCache)
|
|
{
|
|
return s_binderCache[(int)flags] ?? (s_binderCache[(int)flags] = new PSSwitchClauseEvalBinder(flags));
|
|
}
|
|
}
|
|
|
|
private PSSwitchClauseEvalBinder(SwitchFlags flags)
|
|
{
|
|
_flags = flags;
|
|
}
|
|
|
|
public override Type ReturnType { get { return typeof(bool); } }
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
// target is the condition
|
|
// args[0] is the value to test against the condition
|
|
// args[1] is the execution context to use, if needed
|
|
|
|
if (!target.HasValue || !args[0].HasValue)
|
|
{
|
|
return Defer(target, args[0], args[1]).WriteToDebugLog(this);
|
|
}
|
|
|
|
// No need to add binding restrictions on either arg, the type of the clause is all that matters.
|
|
// We can skip the restrictions on the arg, the generated code will contain a dynamic site to convert
|
|
// the arg to string if applicable. If the dynamic site is removed, then arg restrictions must be added.
|
|
var targetRestrictions = target.PSGetTypeRestriction();
|
|
|
|
// If any args are PSObject, we typically call DeferForPSObject, but in this case,
|
|
// we don't because args[0] should not be unwrapped, which DeferForPSObject would do.
|
|
if (target.Value is PSObject)
|
|
{
|
|
return (new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(this, this.ReturnType,
|
|
Expression.Call(CachedReflectionInfo.PSObject_Base,
|
|
target.Expression.Cast(typeof(object))),
|
|
args[0].Expression,
|
|
args[1].Expression),
|
|
targetRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value == null)
|
|
{
|
|
// If the condition is null, the we simply test the value against null. It seems like
|
|
// this is a silly thing to allow in a switch, maybe it should be disallowed in strict mode.
|
|
return (new DynamicMetaObject(
|
|
Expression.Equal(args[0].Expression.Cast(typeof(object)), ExpressionCache.NullConstant),
|
|
target.PSGetTypeRestriction())).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value is ScriptBlock)
|
|
{
|
|
var call = Expression.Call(target.Expression.Cast(typeof(ScriptBlock)),
|
|
CachedReflectionInfo.ScriptBlock_DoInvokeReturnAsIs,
|
|
/*useLocalScope=*/ ExpressionCache.Constant(true),
|
|
/*errorHandlingBehavior=*/ Expression.Constant(ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe),
|
|
/*dollarUnder=*/ args[0].CastOrConvert(typeof(object)),
|
|
/*input=*/ ExpressionCache.AutomationNullConstant,
|
|
/*scriptThis=*/ ExpressionCache.AutomationNullConstant,
|
|
/*args=*/ ExpressionCache.NullObjectArray);
|
|
|
|
return (new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(PSConvertBinder.Get(typeof(bool)), typeof(bool), call),
|
|
targetRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
// From here on out, arg must be a string.
|
|
var executionContext = args[1].Expression;
|
|
var argAsString = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), args[0].Expression,
|
|
executionContext);
|
|
|
|
if (target.Value is Regex || (_flags & SwitchFlags.Regex) != 0)
|
|
{
|
|
var call = Expression.Call(CachedReflectionInfo.SwitchOps_ConditionSatisfiedRegex,
|
|
/*caseSensitive=*/ ExpressionCache.Constant((_flags & SwitchFlags.CaseSensitive) != 0),
|
|
/*condition=*/ target.Expression.Cast(typeof(object)),
|
|
/*errorPosition=*/ ExpressionCache.NullExtent,
|
|
/*str=*/ argAsString,
|
|
/*context=*/ executionContext);
|
|
|
|
return (new DynamicMetaObject(call, targetRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value is WildcardPattern || (_flags & SwitchFlags.Wildcard) != 0)
|
|
{
|
|
var call = Expression.Call(CachedReflectionInfo.SwitchOps_ConditionSatisfiedWildcard,
|
|
/*caseSensitive=*/ ExpressionCache.Constant((_flags & SwitchFlags.CaseSensitive) != 0),
|
|
/*condition=*/ target.Expression.Cast(typeof(object)),
|
|
/*str=*/ argAsString,
|
|
/*context=*/ executionContext);
|
|
|
|
// Binding restrictions must test the target, even if the switch is in -regex mode.
|
|
// If we didn't add restrictions on the target, we'd incorrectly use this rule when the target
|
|
// is a script block.
|
|
// We can skip the restrictions on the arg, the generated code contains a dynamic site to convert
|
|
// the arg to string, so that site handles any arg type properly.
|
|
return (new DynamicMetaObject(call, targetRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
var targetAsString = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), target.Expression,
|
|
executionContext);
|
|
return (new DynamicMetaObject(
|
|
Compiler.CallStringEquals(targetAsString, argAsString, ((_flags & SwitchFlags.CaseSensitive) == 0)),
|
|
targetRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
// This class implements the standard binder CreateInstanceBinder, but this binder handles the CallInfo a little differently.
|
|
// The ArgumentNames are not used to invoke a constructor, instead they are used to set properties/fields in the attribute.
|
|
internal sealed class PSAttributeGenerator : CreateInstanceBinder
|
|
{
|
|
private static readonly Dictionary<CallInfo, PSAttributeGenerator> s_binderCache =
|
|
new Dictionary<CallInfo, PSAttributeGenerator>();
|
|
|
|
internal static PSAttributeGenerator Get(CallInfo callInfo)
|
|
{
|
|
lock (s_binderCache)
|
|
{
|
|
PSAttributeGenerator binder;
|
|
if (!s_binderCache.TryGetValue(callInfo, out binder))
|
|
{
|
|
binder = new PSAttributeGenerator(callInfo);
|
|
s_binderCache.Add(callInfo, binder);
|
|
}
|
|
|
|
return binder;
|
|
}
|
|
}
|
|
|
|
private PSAttributeGenerator(CallInfo callInfo)
|
|
: base(callInfo)
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
Diagnostics.Assert(target.HasValue && target.Value is Type, "caller to verify arguments");
|
|
var attributeType = (Type)target.Value;
|
|
var ctorInfos = attributeType.GetConstructors();
|
|
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctorInfos);
|
|
|
|
// We can't use a type restriction on target, it's always a System.Type. So make sure we always use an instance restriction
|
|
target = new DynamicMetaObject(target.Expression, BindingRestrictions.GetInstanceRestriction(target.Expression, target.Value), target.Value);
|
|
|
|
string errorId = null;
|
|
string errorMsg = null;
|
|
bool expandParamsOnBest;
|
|
bool callNonVirtually;
|
|
var positionalArgCount = CallInfo.ArgumentCount - CallInfo.ArgumentNames.Count;
|
|
|
|
var bestMethod = Adapter.FindBestMethod(
|
|
newConstructors,
|
|
invocationConstraints: null,
|
|
allowCastingToByRefLikeType: false,
|
|
args.Take(positionalArgCount).Select(static arg => arg.Value).ToArray(),
|
|
ref errorId,
|
|
ref errorMsg,
|
|
out expandParamsOnBest,
|
|
out callNonVirtually);
|
|
|
|
if (bestMethod == null)
|
|
{
|
|
return errorSuggestion ?? new DynamicMetaObject(
|
|
Expression.Throw(
|
|
Expression.New(CachedReflectionInfo.MethodException_ctor, Expression.Constant(errorId),
|
|
Expression.Constant(null, typeof(Exception)), Expression.Constant(errorMsg),
|
|
Expression.NewArrayInit(typeof(object),
|
|
Expression.Constant(".ctor").Cast(typeof(object)),
|
|
ExpressionCache.Constant(positionalArgCount).Cast(typeof(object)))),
|
|
this.ReturnType),
|
|
target.CombineRestrictions(args));
|
|
}
|
|
|
|
var constructorInfo = (ConstructorInfo)bestMethod.method;
|
|
var parameterInfo = constructorInfo.GetParameters();
|
|
var ctorArgs = new Expression[parameterInfo.Length];
|
|
var argIndex = 0;
|
|
for (; argIndex < parameterInfo.Length; ++argIndex)
|
|
{
|
|
var resultType = parameterInfo[argIndex].ParameterType;
|
|
|
|
// The extension method 'CustomAttributeExtensions.GetCustomAttributes(ParameterInfo, Type, Boolean)' has inconsistent
|
|
// behavior on its return value in both FullCLR and CoreCLR. According to MSDN, if the attribute cannot be found, it
|
|
// should return an empty collection. However, it returns null in some rare cases [when the parameter isn't backed by
|
|
// actual metadata].
|
|
// This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling
|
|
// against CoreCLR. So we need to add a null check until this is fixed in CLR.
|
|
var paramArrayAttrs = parameterInfo[argIndex].GetCustomAttributes(typeof(ParamArrayAttribute), true);
|
|
if (paramArrayAttrs != null && paramArrayAttrs.Length > 0 && expandParamsOnBest)
|
|
{
|
|
var elementType = parameterInfo[argIndex].ParameterType.GetElementType();
|
|
var paramsArray = new List<Expression>();
|
|
|
|
int ctorArgIndex = argIndex;
|
|
for (int i = argIndex; i < positionalArgCount; ++i, ++argIndex)
|
|
{
|
|
bool debase;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion are done by constructors with params arguments.
|
|
// Protection against conversions are covered by the method resolution algorithm
|
|
// (which ignores method arguments with disallowed types)
|
|
var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, elementType, out debase);
|
|
Diagnostics.Assert(conversion.Rank != ConversionRank.None, "FindBestMethod should have failed if there is no conversion");
|
|
|
|
paramsArray.Add(
|
|
PSConvertBinder.InvokeConverter(conversion, args[i].Expression, elementType, debase, ExpressionCache.InvariantCulture));
|
|
}
|
|
|
|
ctorArgs[ctorArgIndex] = Expression.NewArrayInit(elementType, paramsArray);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, resultType, out bool debase);
|
|
ctorArgs[argIndex] = PSConvertBinder.InvokeConverter(conversion, args[argIndex].Expression, resultType, debase, ExpressionCache.InvariantCulture);
|
|
}
|
|
}
|
|
|
|
Expression result = Expression.New(constructorInfo, ctorArgs);
|
|
if (CallInfo.ArgumentNames.Count > 0)
|
|
{
|
|
var tmp = Expression.Parameter(result.Type);
|
|
var blockExprs = new List<Expression>();
|
|
foreach (var name in CallInfo.ArgumentNames)
|
|
{
|
|
var members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property,
|
|
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
|
if (members.Length != 1
|
|
|| (members[0] is not PropertyInfo && members[0] is not FieldInfo))
|
|
{
|
|
return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "PropertyNotFoundForType",
|
|
ParserStrings.PropertyNotFoundForType, Expression.Constant(name),
|
|
Expression.Constant(attributeType, typeof(Type)));
|
|
}
|
|
|
|
var member = members[0];
|
|
Expression lhs;
|
|
Type propertyType;
|
|
var propertyInfo = member as PropertyInfo;
|
|
if (propertyInfo != null)
|
|
{
|
|
if (propertyInfo.GetSetMethod() == null)
|
|
{
|
|
return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "PropertyIsReadOnly",
|
|
ParserStrings.PropertyIsReadOnly, Expression.Constant(name));
|
|
}
|
|
|
|
propertyType = propertyInfo.PropertyType;
|
|
lhs = Expression.Property(tmp.Cast(propertyInfo.DeclaringType), propertyInfo);
|
|
}
|
|
else
|
|
{
|
|
propertyType = ((FieldInfo)member).FieldType;
|
|
lhs = Expression.Field(tmp.Cast(member.DeclaringType), (FieldInfo)member);
|
|
}
|
|
|
|
bool debase;
|
|
|
|
// ConstrainedLanguage note - calls to these property assignment conversions are enforced by the
|
|
// property assignment binding rules (which disallow property conversions to disallowed types)
|
|
var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, propertyType, out debase);
|
|
if (conversion.Rank == ConversionRank.None)
|
|
{
|
|
return PSConvertBinder.ThrowNoConversion(args[argIndex], propertyType, this, -1,
|
|
args.Except(new DynamicMetaObject[] { args[argIndex] }).Prepend(target).ToArray());
|
|
}
|
|
|
|
blockExprs.Add(
|
|
Expression.Assign(lhs, PSConvertBinder.InvokeConverter(conversion, args[argIndex].Expression, propertyType, debase, ExpressionCache.InvariantCulture)));
|
|
argIndex += 1;
|
|
}
|
|
|
|
// We wrap the block of assignments in a try/catch and issue a general error message whenever the assignment fails.
|
|
var exception = Expression.Parameter(typeof(Exception));
|
|
result = Expression.Block(
|
|
new ParameterExpression[] { tmp },
|
|
Expression.Assign(tmp, result),
|
|
Expression.TryCatch(
|
|
Expression.Block(typeof(void), blockExprs),
|
|
Expression.Catch(
|
|
exception,
|
|
Compiler.ThrowRuntimeErrorWithInnerException("PropertyAssignmentException", Expression.Property(exception, "Message"), exception, typeof(void)))),
|
|
// The result of the block is the object constructed, so the tmp must be the last expr in the block.
|
|
tmp);
|
|
}
|
|
|
|
return new DynamicMetaObject(result, target.CombineRestrictions(args));
|
|
}
|
|
}
|
|
|
|
internal sealed class PSCustomObjectConverter : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSCustomObjectConverter s_binder = new PSCustomObjectConverter();
|
|
|
|
internal static PSCustomObjectConverter Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSCustomObjectConverter()
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
var baseObjectValue = PSObject.Base(target.Value);
|
|
var toType = baseObjectValue is OrderedDictionary || baseObjectValue is Hashtable
|
|
? typeof(LanguagePrimitives.InternalPSCustomObject)
|
|
: typeof(PSObject);
|
|
|
|
// ConstrainedLanguage note - calls to this conversion only target PSCustomObject / PSObject,
|
|
// which is safe.
|
|
bool debase;
|
|
var conversion = LanguagePrimitives.FigureConversion(target.Value, toType, out debase);
|
|
|
|
return new DynamicMetaObject(
|
|
PSConvertBinder.InvokeConverter(conversion, target.Expression, toType, debase, ExpressionCache.InvariantCulture),
|
|
target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
internal sealed class PSDynamicConvertBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSDynamicConvertBinder s_binder = new PSDynamicConvertBinder();
|
|
|
|
internal static PSDynamicConvertBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSDynamicConvertBinder()
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
// target is the type and is never a PSObject
|
|
// arg is the object to be converted
|
|
|
|
DynamicMetaObject arg = args[0];
|
|
if (!target.HasValue || !arg.HasValue)
|
|
{
|
|
return Defer(target, arg).WriteToDebugLog(this);
|
|
}
|
|
|
|
var toType = target.Value as Type;
|
|
Diagnostics.Assert(toType != null, "target must be a type");
|
|
|
|
var bindingRestrictions = BindingRestrictions.GetInstanceRestriction(target.Expression, toType).Merge(arg.PSGetTypeRestriction());
|
|
|
|
return new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(PSConvertBinder.Get(toType), toType, arg.Expression).Cast(typeof(object)),
|
|
bindingRestrictions).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This binder is used to copy mutable value types when assigning to variables, otherwise just assigning the target object directly.
|
|
/// </summary>
|
|
internal sealed class PSVariableAssignmentBinder : DynamicMetaObjectBinder
|
|
{
|
|
private static readonly PSVariableAssignmentBinder s_binder = new PSVariableAssignmentBinder();
|
|
internal static int s_mutableValueWithInstanceMemberVersion;
|
|
|
|
internal static PSVariableAssignmentBinder Get()
|
|
{
|
|
return s_binder;
|
|
}
|
|
|
|
private PSVariableAssignmentBinder()
|
|
{
|
|
CacheTarget((Func<CallSite, object, object>)(PSObjectStringRule));
|
|
CacheTarget((Func<CallSite, object, object>)(ObjectRule));
|
|
CacheTarget((Func<CallSite, object, object>)(IntRule));
|
|
CacheTarget((Func<CallSite, object, object>)(EnumRule));
|
|
CacheTarget((Func<CallSite, object, object>)(BoolRule));
|
|
CacheTarget((Func<CallSite, object, object>)(NullRule));
|
|
}
|
|
|
|
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
var value = target.Value;
|
|
|
|
if (value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.NullConstant, target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
|
|
var psobj = value as PSObject;
|
|
if (psobj != null)
|
|
{
|
|
var restrictions = BindingRestrictions.GetTypeRestriction(target.Expression, psobj.GetType());
|
|
var expr = target.Expression;
|
|
var baseObject = psobj.BaseObject;
|
|
var baseObjExpr = Expression.Property(expr.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_BaseObject);
|
|
if (baseObject != null)
|
|
{
|
|
var baseObjType = baseObject.GetType();
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetTypeRestriction(baseObjExpr, baseObjType));
|
|
if (baseObjType.IsValueType)
|
|
{
|
|
expr = GetExprForValueType(baseObjType, Expression.Convert(baseObjExpr, baseObjType), expr, ref restrictions);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
restrictions =
|
|
restrictions.Merge(
|
|
BindingRestrictions.GetExpressionRestriction(Expression.Equal(baseObjExpr,
|
|
ExpressionCache.NullConstant)));
|
|
}
|
|
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var type = value.GetType();
|
|
if (type.IsValueType)
|
|
{
|
|
var expr = target.Expression;
|
|
var restrictions = target.PSGetTypeRestriction();
|
|
expr = GetExprForValueType(type, Expression.Convert(expr, type), expr, ref restrictions);
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
// This rule is meant to cover all classes except PSObject.
|
|
return new DynamicMetaObject(
|
|
target.Expression, BindingRestrictions.GetExpressionRestriction(
|
|
Expression.AndAlso(
|
|
Expression.Not(Expression.TypeIs(target.Expression, typeof(ValueType))),
|
|
Expression.Not(Expression.TypeIs(target.Expression, typeof(PSObject)))))).WriteToDebugLog(this);
|
|
}
|
|
|
|
private static Expression GetExprForValueType(Type type,
|
|
Expression convertedExpr,
|
|
Expression originalExpr,
|
|
ref BindingRestrictions restrictions)
|
|
{
|
|
// A binding restriction for value types will be either:
|
|
// if (obj is SomeValueType)
|
|
// or
|
|
// if (obj is SomeValueType && _mutableValueWithInstanceMemberVersion == someVersionNumber)
|
|
//
|
|
// And the expression will be one of 3 possibilities:
|
|
// * return expr
|
|
// * tmp = expr; return tmp (make a copy)
|
|
// * return CopyInstanceMembersOfValueType((T)expr, expr) (make a copy, also copy instance members)
|
|
//
|
|
// If we've never seen an instance member for a given type, we can avoid the expensive call to
|
|
// CopyInstanceMembersOfValueType, but if somebody adds an instance member in the future, we need to invalidate
|
|
// previously generated rules. We do that with the version check.
|
|
//
|
|
// We can skip a version check if we're already generating the worst case code. This also avoids regenerating
|
|
// new bindings when the new binding won't differ from a previous binding.
|
|
|
|
Expression expr;
|
|
bool needVersionCheck = true;
|
|
if (s_mutableValueTypesWithInstanceMembers.ContainsKey(type))
|
|
{
|
|
var genericMethodInfo = CachedReflectionInfo.PSVariableAssignmentBinder_CopyInstanceMembersOfValueType.MakeGenericMethod(new Type[] { type });
|
|
expr = Expression.Call(genericMethodInfo, convertedExpr, originalExpr);
|
|
needVersionCheck = false;
|
|
}
|
|
else if (IsValueTypeMutable(type))
|
|
{
|
|
var temp = Expression.Variable(type);
|
|
expr = Expression.Block(new[] { temp },
|
|
// The valuetype is mutable, so force a copy by assigning to a temp.
|
|
Expression.Assign(temp, convertedExpr),
|
|
// Box the return value because the dynamic site expects object
|
|
temp.Cast(typeof(object)));
|
|
}
|
|
else
|
|
{
|
|
expr = originalExpr;
|
|
}
|
|
|
|
if (needVersionCheck)
|
|
{
|
|
restrictions = restrictions.Merge(GetVersionCheck(s_mutableValueWithInstanceMemberVersion));
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
private static object EnumRule(CallSite site, object obj)
|
|
{
|
|
if (obj is Enum) { return obj; }
|
|
|
|
return ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static object BoolRule(CallSite site, object obj)
|
|
{
|
|
if (obj is bool) { return obj; }
|
|
|
|
return ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static object IntRule(CallSite site, object obj)
|
|
{
|
|
if (obj is int) { return obj; }
|
|
|
|
return ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static object ObjectRule(CallSite site, object obj)
|
|
{
|
|
if (obj is not ValueType && obj is not PSObject) { return obj; }
|
|
|
|
return ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static object PSObjectStringRule(CallSite site, object obj)
|
|
{
|
|
var psobj = obj as PSObject;
|
|
if (psobj != null && psobj.BaseObject is string) return obj;
|
|
return ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
private static object NullRule(CallSite site, object obj)
|
|
{
|
|
return obj == null ? null : ((CallSite<Func<CallSite, object, object>>)site).Update(site, obj);
|
|
}
|
|
|
|
internal static bool IsValueTypeMutable(Type type)
|
|
{
|
|
// First, check for enums/primitives and compiler-defined attributes.
|
|
if (type.IsPrimitive
|
|
|| type.IsEnum
|
|
|| type.IsDefined(typeof(System.Runtime.CompilerServices.IsReadOnlyAttribute), inherit: false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the builtin attribute is not present, check for a custom attribute from by the compiler. If the
|
|
// library targets netstandard2.0, the compiler can't be sure the attribute will be provided by the runtime,
|
|
// and defines its own attribute of the same name during compilation. To account for this, we must check the
|
|
// type by name, not by reference.
|
|
foreach (object attribute in type.GetCustomAttributes(inherit: false))
|
|
{
|
|
if (attribute.GetType().FullName.Equals(
|
|
"System.Runtime.CompilerServices.IsReadOnlyAttribute",
|
|
StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Fallback: check all fields (public + private) to verify whether they're all readonly.
|
|
// If any field is not readonly, the value type is potentially mutable.
|
|
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
{
|
|
if (!field.IsInitOnly)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If all fields are init-only (read-only), then the value type is immutable.
|
|
return false;
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<Type, bool> s_mutableValueTypesWithInstanceMembers =
|
|
new ConcurrentDictionary<Type, bool>();
|
|
|
|
internal static void NoteTypeHasInstanceMemberOrTypeName(Type type)
|
|
{
|
|
if (!type.IsValueType || !IsValueTypeMutable(type))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (s_mutableValueTypesWithInstanceMembers.TryAdd(type, true))
|
|
{
|
|
s_mutableValueWithInstanceMemberVersion += 1;
|
|
}
|
|
}
|
|
|
|
internal static object CopyInstanceMembersOfValueType<T>(T t, object boxedT) where T : struct
|
|
{
|
|
PSMemberInfoInternalCollection<PSMemberInfo> unused1;
|
|
ConsolidatedString unused2;
|
|
if (PSObject.HasInstanceMembers(boxedT, out unused1) || PSObject.HasInstanceTypeName(boxedT, out unused2))
|
|
{
|
|
var psobj = PSObject.AsPSObject(boxedT);
|
|
return PSObject.Base(psobj.Copy());
|
|
}
|
|
|
|
// We want a copy (because the value type is mutable), so return t, not boxedT.
|
|
return t;
|
|
}
|
|
|
|
internal static BindingRestrictions GetVersionCheck(int expectedVersionNumber)
|
|
{
|
|
return BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Equal(Expression.Field(null, CachedReflectionInfo.PSVariableAssignmentBinder__mutableValueWithInstanceMemberVersion),
|
|
ExpressionCache.Constant(expectedVersionNumber)));
|
|
}
|
|
}
|
|
|
|
#endregion PowerShell non-standard language binders
|
|
|
|
#region Standard binders
|
|
|
|
/// <summary>
|
|
/// The binder for common binary operators. PowerShell specific binary operators are handled elsewhere.
|
|
/// </summary>
|
|
internal sealed class PSBinaryOperationBinder : BinaryOperationBinder
|
|
{
|
|
#region Constructors and factory methods
|
|
|
|
private static readonly Dictionary<Tuple<ExpressionType, bool, bool>, PSBinaryOperationBinder> s_binderCache =
|
|
new Dictionary<Tuple<ExpressionType, bool, bool>, PSBinaryOperationBinder>();
|
|
|
|
internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignoreCase = true, bool scalarCompare = false)
|
|
{
|
|
PSBinaryOperationBinder result;
|
|
lock (s_binderCache)
|
|
{
|
|
var key = Tuple.Create(operation, ignoreCase, scalarCompare);
|
|
if (!s_binderCache.TryGetValue(key, out result))
|
|
{
|
|
result = new PSBinaryOperationBinder(operation, ignoreCase, scalarCompare);
|
|
s_binderCache.Add(key, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private readonly bool _ignoreCase;
|
|
private readonly bool _scalarCompare;
|
|
internal int _version;
|
|
|
|
private PSBinaryOperationBinder(ExpressionType operation, bool ignoreCase, bool scalarCompare)
|
|
: base(operation)
|
|
{
|
|
_ignoreCase = ignoreCase;
|
|
_scalarCompare = scalarCompare;
|
|
this._version = 0;
|
|
}
|
|
|
|
#endregion Constructors and factory methods
|
|
|
|
#region Delegates for enumerable comparisons
|
|
|
|
private Func<object, object, bool> _compareDelegate;
|
|
|
|
private Func<object, object, bool> GetScalarCompareDelegate()
|
|
{
|
|
if (_compareDelegate == null)
|
|
{
|
|
var lvalExpr = Expression.Parameter(typeof(object), "lval");
|
|
var rvalExpr = Expression.Parameter(typeof(object), "rval");
|
|
|
|
var compareDelegate = Expression.Lambda<Func<object, object, bool>>(
|
|
DynamicExpression.Dynamic(Get(Operation, _ignoreCase, scalarCompare: true),
|
|
typeof(object), lvalExpr, rvalExpr).Cast(typeof(bool)),
|
|
lvalExpr, rvalExpr).Compile();
|
|
Interlocked.CompareExchange(ref _compareDelegate, compareDelegate, null);
|
|
}
|
|
|
|
return _compareDelegate;
|
|
}
|
|
|
|
#endregion Delegates for enumerable comparisons
|
|
|
|
public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || !arg.HasValue)
|
|
{
|
|
return Defer(target, arg).WriteToDebugLog(this);
|
|
}
|
|
|
|
if ((target.Value is PSObject && PSObject.Base(target.Value) != target.Value) ||
|
|
(arg.Value is PSObject && PSObject.Base(arg.Value) != arg.Value))
|
|
{
|
|
// When adding to an array, we don't want to unwrap the RHS - it's unnecessary,
|
|
// and in the case of strings, we actually lose instance members on the PSObject.
|
|
if (!(Operation == ExpressionType.Add && PSEnumerableBinder.IsEnumerable(target) != null))
|
|
{
|
|
// Defer when the object is wrapped, but not for empty objects.
|
|
return this.DeferForPSObject(target, arg).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
switch (Operation)
|
|
{
|
|
case ExpressionType.Add:
|
|
return BinaryAdd(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Subtract:
|
|
return BinarySub(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Multiply:
|
|
return BinaryMultiply(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Divide:
|
|
return BinaryDivide(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Modulo:
|
|
return BinaryRemainder(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.And:
|
|
return BinaryBitwiseAnd(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Or:
|
|
return BinaryBitwiseOr(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.ExclusiveOr:
|
|
return BinaryBitwiseXor(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Equal:
|
|
return CompareEQ(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.NotEqual:
|
|
return CompareNE(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.GreaterThan:
|
|
return CompareGT(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.GreaterThanOrEqual:
|
|
return CompareGE(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.LessThan:
|
|
return CompareLT(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.LessThanOrEqual:
|
|
return CompareLE(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.LeftShift:
|
|
return LeftShift(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.RightShift:
|
|
return RightShift(target, arg, errorSuggestion).WriteToDebugLog(this);
|
|
}
|
|
|
|
return (errorSuggestion ??
|
|
new DynamicMetaObject(
|
|
Compiler.CreateThrow(typeof(object), typeof(PSNotImplementedException), "Unimplemented operation"),
|
|
target.CombineRestrictions(arg))).WriteToDebugLog(this);
|
|
}
|
|
|
|
#region Helpers
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "PSBinaryOperationBinder {0}{1} ver:{2}", GetOperatorText(), _scalarCompare ? " scalarOnly" : string.Empty, _version);
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSBinaryOperationBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetOperatorText()
|
|
{
|
|
switch (Operation)
|
|
{
|
|
case ExpressionType.Add: return TokenKind.Plus.Text();
|
|
case ExpressionType.Subtract: return TokenKind.Minus.Text();
|
|
case ExpressionType.Multiply: return TokenKind.Multiply.Text();
|
|
case ExpressionType.Divide: return TokenKind.Divide.Text();
|
|
case ExpressionType.Modulo: return TokenKind.Rem.Text();
|
|
case ExpressionType.And: return TokenKind.Band.Text();
|
|
case ExpressionType.Or: return TokenKind.Bor.Text();
|
|
case ExpressionType.ExclusiveOr: return TokenKind.Bxor.Text();
|
|
case ExpressionType.Equal: return _ignoreCase ? TokenKind.Ieq.Text() : TokenKind.Ceq.Text();
|
|
case ExpressionType.NotEqual: return _ignoreCase ? TokenKind.Ine.Text() : TokenKind.Cne.Text();
|
|
case ExpressionType.GreaterThan: return _ignoreCase ? TokenKind.Igt.Text() : TokenKind.Cgt.Text();
|
|
case ExpressionType.GreaterThanOrEqual: return _ignoreCase ? TokenKind.Ige.Text() : TokenKind.Cge.Text();
|
|
case ExpressionType.LessThan: return _ignoreCase ? TokenKind.Ilt.Text() : TokenKind.Clt.Text();
|
|
case ExpressionType.LessThanOrEqual: return _ignoreCase ? TokenKind.Ile.Text() : TokenKind.Cle.Text();
|
|
case ExpressionType.LeftShift: return TokenKind.Shl.Text();
|
|
case ExpressionType.RightShift: return TokenKind.Shr.Text();
|
|
}
|
|
|
|
Diagnostics.Assert(false, "Unexpected operator");
|
|
return string.Empty;
|
|
}
|
|
|
|
private static DynamicMetaObject CallImplicitOp(string methodName, DynamicMetaObject target, DynamicMetaObject arg, string errorOperator, DynamicMetaObject errorSuggestion)
|
|
{
|
|
// We will assume that if we got here with a non-null errorSuggestion and DynamicObject, that we
|
|
// are trying to generate the expression that calls the override to DynamicObject.TryBinaryOperation.
|
|
// We get called twice for the same target object, once with a null errorSuggestion (in which case we'll have
|
|
// returned the result below), and then a second time with a non-null errorSuggestion, which we return as is.
|
|
if (errorSuggestion != null && target.Value is DynamicObject)
|
|
{
|
|
return errorSuggestion;
|
|
}
|
|
|
|
// TODO: use a dynamic call site to invoke the correct method or throw an error.
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ParserOps_ImplicitOp,
|
|
target.Expression.Cast(typeof(object)),
|
|
arg.Expression.Cast(typeof(object)),
|
|
Expression.Constant(methodName),
|
|
ExpressionCache.NullExtent,
|
|
Expression.Constant(errorOperator)),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
private static bool IsValueNegative(object value, TypeCode typeCode)
|
|
{
|
|
switch (typeCode)
|
|
{
|
|
case TypeCode.SByte: return (sbyte)value < 0;
|
|
case TypeCode.Int16: return (short)value < 0;
|
|
case TypeCode.Int32: return (int)value < 0;
|
|
case TypeCode.Int64: return (long)value < 0;
|
|
}
|
|
|
|
Diagnostics.Assert(false, "Invalid type code for testing negative value");
|
|
return true;
|
|
}
|
|
|
|
private static Expression TypedZero(TypeCode typeCode)
|
|
{
|
|
switch (typeCode)
|
|
{
|
|
case TypeCode.SByte: return Expression.Constant((sbyte)0);
|
|
case TypeCode.Int16: return Expression.Constant((short)0);
|
|
case TypeCode.Int32: return ExpressionCache.Constant(0);
|
|
case TypeCode.Int64: return Expression.Constant((long)0);
|
|
}
|
|
|
|
Diagnostics.Assert(false, "Invalid type code for testing negative value");
|
|
return null;
|
|
}
|
|
|
|
private static DynamicMetaObject FigureSignedUnsignedInt(DynamicMetaObject obj, TypeCode typeCode, TypeCode currentOpType, out Type opImplType, out Type argType, out bool shouldFallbackToDoubleInCaseOfOverflow)
|
|
{
|
|
opImplType = null;
|
|
argType = null;
|
|
shouldFallbackToDoubleInCaseOfOverflow = false;
|
|
|
|
if (IsValueNegative(obj.Value, typeCode))
|
|
{
|
|
switch (currentOpType)
|
|
{
|
|
case TypeCode.UInt32:
|
|
opImplType = typeof(LongOps);
|
|
argType = typeof(long);
|
|
break;
|
|
case TypeCode.UInt64:
|
|
opImplType = typeof(DecimalOps);
|
|
argType = typeof(decimal);
|
|
// multiply operation may overflow within the [decimal] context, i.e. [int64]::minvalue * [uint64]::maxvalue.
|
|
// if overflow happens, we fallback to the [double] context
|
|
shouldFallbackToDoubleInCaseOfOverflow = true;
|
|
break;
|
|
|
|
default:
|
|
Diagnostics.Assert(false, "Need to figure out opImplType only for UIn32 and UInt64");
|
|
break;
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
obj.Expression,
|
|
obj.PSGetTypeRestriction()
|
|
.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.LessThan(obj.Expression.Cast(obj.LimitType), TypedZero(typeCode)))),
|
|
obj.Value);
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
obj.Expression,
|
|
obj.PSGetTypeRestriction()
|
|
.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.GreaterThanOrEqual(obj.Expression.Cast(obj.LimitType), TypedZero(typeCode)))),
|
|
obj.Value);
|
|
}
|
|
|
|
private DynamicMetaObject BinaryNumericOp(string methodName, DynamicMetaObject target, DynamicMetaObject arg)
|
|
{
|
|
// The type code comparison and the code generated by this routine only supports primitive types
|
|
// for both operands.
|
|
Diagnostics.Assert(target.LimitType.IsNumericOrPrimitive() && arg.LimitType.IsNumericOrPrimitive(),
|
|
"numeric ops are only supported with primitive types");
|
|
|
|
Type opImplType = null, argType = null;
|
|
bool fallbackToDoubleInCaseOfOverflow = false;
|
|
|
|
TypeCode leftTypeCode = LanguagePrimitives.GetTypeCode(target.LimitType);
|
|
TypeCode rightTypeCode = LanguagePrimitives.GetTypeCode(arg.LimitType);
|
|
TypeCode opTypeCode = (int)leftTypeCode >= (int)rightTypeCode ? leftTypeCode : rightTypeCode;
|
|
if ((int)opTypeCode <= (int)TypeCode.Int32)
|
|
{
|
|
opImplType = typeof(IntOps);
|
|
argType = typeof(int);
|
|
}
|
|
else if ((int)opTypeCode <= (int)TypeCode.UInt32)
|
|
{
|
|
Diagnostics.Assert(opTypeCode == TypeCode.UInt32, "opType must be UInt32 if it gets in this code path");
|
|
|
|
// If one of the operands is signed, we need to promote to long if the value is negative, but
|
|
// we can stay w/ an integer if the value is positive. Either way, we'll need a type test.
|
|
if (LanguagePrimitives.IsSignedInteger(leftTypeCode))
|
|
{
|
|
target = FigureSignedUnsignedInt(target, leftTypeCode, opTypeCode, out opImplType, out argType, out fallbackToDoubleInCaseOfOverflow);
|
|
}
|
|
else if (LanguagePrimitives.IsSignedInteger(rightTypeCode))
|
|
{
|
|
arg = FigureSignedUnsignedInt(arg, rightTypeCode, opTypeCode, out opImplType, out argType, out fallbackToDoubleInCaseOfOverflow);
|
|
}
|
|
|
|
if (opImplType == null)
|
|
{
|
|
opImplType = typeof(UIntOps);
|
|
argType = typeof(uint);
|
|
}
|
|
}
|
|
else if ((int)opTypeCode <= (int)TypeCode.Int64)
|
|
{
|
|
opImplType = typeof(LongOps);
|
|
argType = typeof(long);
|
|
}
|
|
else if ((int)opTypeCode <= (int)TypeCode.UInt64)
|
|
{
|
|
Diagnostics.Assert(opTypeCode == TypeCode.UInt64, "opType must be UInt64 if it gets in this code path");
|
|
|
|
if (LanguagePrimitives.IsSignedInteger(leftTypeCode))
|
|
{
|
|
target = FigureSignedUnsignedInt(target, leftTypeCode, opTypeCode, out opImplType, out argType, out fallbackToDoubleInCaseOfOverflow);
|
|
}
|
|
else if (LanguagePrimitives.IsSignedInteger(rightTypeCode))
|
|
{
|
|
arg = FigureSignedUnsignedInt(arg, rightTypeCode, opTypeCode, out opImplType, out argType, out fallbackToDoubleInCaseOfOverflow);
|
|
}
|
|
|
|
if (opImplType == null)
|
|
{
|
|
opImplType = typeof(ULongOps);
|
|
argType = typeof(ulong);
|
|
}
|
|
}
|
|
else if (opTypeCode == TypeCode.Decimal)
|
|
{
|
|
if (methodName.StartsWith("Compare", StringComparison.Ordinal))
|
|
{
|
|
// Casting a double to decimal can overflow. Instead, we are "smarter" and avoid
|
|
// the cast, and allow the comparison. There may be a precision problem with values
|
|
// near Decimal.MaxValue or Decimal.MinValue, but V2 allowed the comparisons
|
|
// w/o errors, so we continue to do so.
|
|
if (LanguagePrimitives.IsFloating(leftTypeCode))
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(typeof(DecimalOps).GetMethod(methodName + "1", BindingFlags.NonPublic | BindingFlags.Static),
|
|
target.Expression.Cast(target.LimitType).Cast(typeof(double)),
|
|
arg.Expression.Cast(arg.LimitType).Cast(typeof(decimal))),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
if (LanguagePrimitives.IsFloating(rightTypeCode))
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(typeof(DecimalOps).GetMethod(methodName + "2", BindingFlags.NonPublic | BindingFlags.Static),
|
|
target.Expression.Cast(target.LimitType).Cast(typeof(decimal)),
|
|
arg.Expression.Cast(arg.LimitType).Cast(typeof(double))),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
}
|
|
|
|
opImplType = typeof(DecimalOps);
|
|
argType = typeof(decimal);
|
|
}
|
|
else
|
|
{
|
|
opImplType = typeof(DoubleOps);
|
|
argType = typeof(double);
|
|
}
|
|
|
|
Expression expr =
|
|
Expression.Call(opImplType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static),
|
|
target.Expression.Cast(target.LimitType).Cast(argType),
|
|
arg.Expression.Cast(arg.LimitType).Cast(argType));
|
|
|
|
if (fallbackToDoubleInCaseOfOverflow)
|
|
{
|
|
Type doubleOpType = typeof(DoubleOps);
|
|
Type doubleArgType = typeof(double);
|
|
Expression fallbackToDoubleExpr =
|
|
Expression.Call(doubleOpType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static),
|
|
target.Expression.Cast(target.LimitType).Cast(doubleArgType),
|
|
arg.Expression.Cast(arg.LimitType).Cast(doubleArgType));
|
|
|
|
var exception = Expression.Variable(typeof(RuntimeException), "psBinaryNumericOpException");
|
|
var catchExpr =
|
|
Expression.Catch(
|
|
exception,
|
|
Expression.Block(
|
|
Expression.IfThen(
|
|
Expression.Not(Expression.TypeIs(Expression.Property(exception, "InnerException"), typeof(OverflowException))),
|
|
Expression.Rethrow()),
|
|
fallbackToDoubleExpr)
|
|
);
|
|
|
|
expr = Expression.TryCatch(
|
|
expr,
|
|
catchExpr);
|
|
}
|
|
|
|
if (target.LimitType.IsEnum)
|
|
{
|
|
// Make sure the result type is an enum unless we were expecting a bool.
|
|
switch (Operation)
|
|
{
|
|
case ExpressionType.Equal:
|
|
case ExpressionType.NotEqual:
|
|
case ExpressionType.GreaterThan:
|
|
case ExpressionType.GreaterThanOrEqual:
|
|
case ExpressionType.LessThan:
|
|
case ExpressionType.LessThanOrEqual:
|
|
break;
|
|
default:
|
|
expr = expr.Cast(target.LimitType).Cast(typeof(object));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Operation == ExpressionType.Equal || Operation == ExpressionType.NotEqual)
|
|
{
|
|
expr = Expression.TryCatch(expr,
|
|
Expression.Catch(typeof(InvalidCastException),
|
|
Operation == ExpressionType.Equal ? ExpressionCache.BoxedFalse : ExpressionCache.BoxedTrue));
|
|
}
|
|
|
|
return new DynamicMetaObject(expr, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
private DynamicMetaObject BinaryNumericStringOp(DynamicMetaObject target, DynamicMetaObject arg)
|
|
{
|
|
// We can't determine the operation type at compile time, we'll need another dynamic site
|
|
// Generate:
|
|
// tmp = Parser.ScanNumber(arg)
|
|
// if (tmp == null) { throw new RuntimeError("BadNumericConstant") }
|
|
// dynamic op target tmp
|
|
// For equality comparison operators, if the conversion fails, we shouldn't raise
|
|
// an exception, so those must be wrapped in try/catch, like:
|
|
// try { /* as above */ }
|
|
// catch (InvalidCastException) { true or false (depending on Operation type) }
|
|
|
|
List<ParameterExpression> temps = new List<ParameterExpression>();
|
|
List<Expression> stmts = new List<Expression>();
|
|
Expression targetExpr = target.Expression;
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
Diagnostics.Assert(
|
|
Operation == ExpressionType.Subtract || Operation == ExpressionType.Divide
|
|
|| Operation == ExpressionType.Modulo || Operation == ExpressionType.And
|
|
|| Operation == ExpressionType.Or || Operation == ExpressionType.ExclusiveOr
|
|
|| Operation == ExpressionType.LeftShift || Operation == ExpressionType.RightShift,
|
|
"string lhs not allowed for operation");
|
|
|
|
targetExpr = ConvertStringToNumber(target.Expression, arg.LimitType);
|
|
}
|
|
|
|
var argExpr = arg.LimitType == typeof(string) ? ConvertStringToNumber(arg.Expression, target.LimitType) : arg.Expression;
|
|
|
|
stmts.Add(
|
|
DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(Operation), typeof(object), targetExpr, argExpr));
|
|
|
|
Expression expr = Expression.Block(temps, stmts);
|
|
if (Operation == ExpressionType.Equal || Operation == ExpressionType.NotEqual)
|
|
{
|
|
expr = Expression.TryCatch(expr,
|
|
Expression.Catch(typeof(InvalidCastException),
|
|
Operation == ExpressionType.Equal ? ExpressionCache.BoxedFalse : ExpressionCache.BoxedTrue));
|
|
}
|
|
|
|
return new DynamicMetaObject(expr, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use the tokenizer to scan a number and convert it to a number of any type.
|
|
/// </summary>
|
|
/// <param name="expr">
|
|
/// The expression that refers to a string to be converted to a number of any type.
|
|
/// </param>
|
|
/// <param name="toType">
|
|
/// Primarily used as part of an error message. If the string is not a number, we want to raise an exception saying the
|
|
/// string can't be converted to this type. Note that if the string is a valid number, it need not be this type.
|
|
/// </param>
|
|
internal static Expression ConvertStringToNumber(Expression expr, Type toType)
|
|
{
|
|
if (!toType.IsNumeric())
|
|
{
|
|
// toType is only mostly for diagnostics, so it doesn't need to be correct. If it's not numeric, we're
|
|
// doing something like "42" - "10", and toType is the type of the "other" operand, in this case, it
|
|
// would string. Fall back to int if Parser.ScanNumber fails.
|
|
toType = typeof(int);
|
|
}
|
|
|
|
return Expression.Call(
|
|
CachedReflectionInfo.Parser_ScanNumber,
|
|
expr.Cast(typeof(string)),
|
|
Expression.Constant(toType, typeof(Type)),
|
|
Expression.Constant(true));
|
|
}
|
|
|
|
private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject arg, Type targetType)
|
|
{
|
|
if (arg.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.Constant(0), arg.PSGetTypeRestriction(), 0);
|
|
}
|
|
|
|
bool boolToDecimal = false;
|
|
if (arg.LimitType.IsNumericOrPrimitive() && !arg.LimitType.IsEnum)
|
|
{
|
|
if (!(targetType == typeof(decimal) && arg.LimitType == typeof(bool)))
|
|
{
|
|
return arg;
|
|
}
|
|
// All other numeric conversions are simple casts, but bool=>decimal is not supported by LINQ (via Convert), so
|
|
// we must do the conversion ourselves.
|
|
boolToDecimal = true;
|
|
}
|
|
|
|
bool debase;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion only target numeric types.
|
|
var conversion = LanguagePrimitives.FigureConversion(arg.Value, targetType, out debase);
|
|
if (conversion.Rank == ConversionRank.ImplicitCast || boolToDecimal || arg.LimitType.IsEnum)
|
|
{
|
|
return new DynamicMetaObject(
|
|
PSConvertBinder.InvokeConverter(conversion, arg.Expression, targetType, debase, ExpressionCache.InvariantCulture),
|
|
arg.PSGetTypeRestriction());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Type GetBitwiseOpType(TypeCode opTypeCode)
|
|
{
|
|
Type opType;
|
|
if ((int)opTypeCode <= (int)TypeCode.Int32) { opType = typeof(int); }
|
|
else if ((int)opTypeCode <= (int)TypeCode.UInt32) { opType = typeof(uint); }
|
|
else if ((int)opTypeCode <= (int)TypeCode.Int64) { opType = typeof(long); }
|
|
// Because we use unsigned for -bnot, to be consistent, we promote to unsigned here too (-band,-bor,-xor)
|
|
else { opType = typeof(ulong); }
|
|
|
|
return opType;
|
|
}
|
|
|
|
#endregion Helpers
|
|
|
|
#region "Arithmetic" operations
|
|
|
|
private DynamicMetaObject BinaryAdd(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(arg.Expression.Cast(typeof(object)), target.CombineRestrictions(arg));
|
|
}
|
|
|
|
if (target.LimitType.IsNumericOrPrimitive() && target.LimitType != typeof(char))
|
|
{
|
|
var numericArg = GetArgAsNumericOrPrimitive(arg, target.LimitType);
|
|
if (numericArg != null)
|
|
{
|
|
return BinaryNumericOp("Add", target, numericArg);
|
|
}
|
|
|
|
if (arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
}
|
|
|
|
Expression lhsStringExpr = null;
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
lhsStringExpr = target.Expression.Cast(typeof(string));
|
|
}
|
|
else if (target.LimitType == typeof(char))
|
|
{
|
|
lhsStringExpr =
|
|
Expression.New(CachedReflectionInfo.String_ctor_char_int,
|
|
target.Expression.Cast(typeof(char)),
|
|
ExpressionCache.Constant(1));
|
|
}
|
|
|
|
if (lhsStringExpr != null)
|
|
{
|
|
// For string concatenation, simply add the 2 strings, possibly converting the rhs first.
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.String_Concat_String,
|
|
lhsStringExpr,
|
|
PSToStringBinder.InvokeToString(
|
|
ExpressionCache.GetExecutionContextFromTLS,
|
|
arg.Expression)),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var lhsEnumerator = PSEnumerableBinder.IsEnumerable(target);
|
|
if (lhsEnumerator != null)
|
|
{
|
|
// target is enumerable, so we're creating a new array.
|
|
|
|
var rhsEnumerator = PSEnumerableBinder.IsEnumerable(arg);
|
|
Expression call;
|
|
if (rhsEnumerator != null)
|
|
{
|
|
// Adding 2 lists
|
|
call = Expression.Call(CachedReflectionInfo.EnumerableOps_AddEnumerable,
|
|
ExpressionCache.GetExecutionContextFromTLS,
|
|
lhsEnumerator.Expression.Cast(typeof(IEnumerator)),
|
|
rhsEnumerator.Expression.Cast(typeof(IEnumerator)));
|
|
}
|
|
else
|
|
{
|
|
// Adding 1 item to a list
|
|
call = Expression.Call(CachedReflectionInfo.EnumerableOps_AddObject,
|
|
ExpressionCache.GetExecutionContextFromTLS,
|
|
lhsEnumerator.Expression.Cast(typeof(IEnumerator)),
|
|
arg.Expression.Cast(typeof(object)));
|
|
}
|
|
|
|
return new DynamicMetaObject(call, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
if (target.Value is IDictionary)
|
|
{
|
|
if (arg.Value is IDictionary)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.HashtableOps_Add,
|
|
target.Expression.Cast(typeof(IDictionary)),
|
|
arg.Expression.Cast(typeof(IDictionary))),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return target.ThrowRuntimeError(new DynamicMetaObject[] { arg }, BindingRestrictions.Empty,
|
|
"AddHashTableToNonHashTable", ParserStrings.AddHashTableToNonHashTable);
|
|
}
|
|
|
|
return CallImplicitOp("op_Addition", target, arg, "+", errorSuggestion);
|
|
}
|
|
|
|
private DynamicMetaObject BinarySub(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinarySubDivOrRem(target, arg, errorSuggestion, "Sub", "op_Subtraction", "-");
|
|
}
|
|
|
|
private DynamicMetaObject BinaryMultiply(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
// Result is null regardless of the arg.
|
|
return new DynamicMetaObject(ExpressionCache.NullConstant, target.PSGetTypeRestriction());
|
|
}
|
|
|
|
if (target.LimitType.IsNumeric())
|
|
{
|
|
var numericArg = GetArgAsNumericOrPrimitive(arg, target.LimitType);
|
|
if (numericArg != null)
|
|
{
|
|
return BinaryNumericOp("Multiply", target, numericArg);
|
|
}
|
|
|
|
if (arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
}
|
|
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
Expression argExpr = arg.LimitType == typeof(string)
|
|
? ConvertStringToNumber(arg.Expression, typeof(int)).Convert(typeof(int))
|
|
: arg.CastOrConvert(typeof(int));
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.StringOps_Multiply,
|
|
target.Expression.Cast(typeof(string)),
|
|
argExpr),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var lhsEnumerator = PSEnumerableBinder.IsEnumerable(target);
|
|
if (lhsEnumerator != null)
|
|
{
|
|
Expression argExpr = arg.LimitType == typeof(string)
|
|
? ConvertStringToNumber(arg.Expression, typeof(int)).Convert(typeof(uint))
|
|
: arg.CastOrConvert(typeof(uint));
|
|
|
|
if (target.LimitType.IsArray)
|
|
{
|
|
var elementType = target.LimitType.GetElementType();
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_Multiply.MakeGenericMethod(elementType),
|
|
target.Expression.Cast(elementType.MakeArrayType()),
|
|
argExpr),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_Multiply,
|
|
lhsEnumerator.Expression,
|
|
argExpr),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return CallImplicitOp("op_Multiply", target, arg, "*", errorSuggestion);
|
|
}
|
|
|
|
private DynamicMetaObject BinaryDivide(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinarySubDivOrRem(target, arg, errorSuggestion, "Divide", "op_Division", "/");
|
|
}
|
|
|
|
private DynamicMetaObject BinaryRemainder(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinarySubDivOrRem(target, arg, errorSuggestion, "Remainder", "op_Modulus", "%");
|
|
}
|
|
|
|
private DynamicMetaObject BinarySubDivOrRem(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion,
|
|
string numericOpMethodName,
|
|
string implicitOpMethodName,
|
|
string errorOperatorText)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
// if target is null, just use 0
|
|
target = new DynamicMetaObject(ExpressionCache.Constant(0), target.PSGetTypeRestriction(), 0);
|
|
}
|
|
|
|
if (target.LimitType.IsNumericOrPrimitive())
|
|
{
|
|
var numericArg = GetArgAsNumericOrPrimitive(arg, target.LimitType);
|
|
if (numericArg != null)
|
|
{
|
|
return BinaryNumericOp(numericOpMethodName, target, numericArg);
|
|
}
|
|
|
|
if (arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
}
|
|
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
// Left is a string. We convert it to a number and try binding again.
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
|
|
return CallImplicitOp(implicitOpMethodName, target, arg, errorOperatorText, errorSuggestion);
|
|
}
|
|
|
|
private DynamicMetaObject Shift(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion, string userOp, Func<Expression, Expression, Expression> exprGenerator)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.Constant(0).Convert(typeof(object)), target.PSGetTypeRestriction());
|
|
}
|
|
|
|
if (target.LimitType == typeof(string) || arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
|
|
var typeCode = LanguagePrimitives.GetTypeCode(target.LimitType);
|
|
if (!target.LimitType.IsNumeric())
|
|
{
|
|
return CallImplicitOp(userOp, target, arg, GetOperatorText(), errorSuggestion);
|
|
}
|
|
|
|
bool debase;
|
|
var resultType = typeof(int);
|
|
|
|
// ConstrainedLanguage note - calls to this conversion only target numeric types.
|
|
var conversion = LanguagePrimitives.FigureConversion(arg.Value, resultType, out debase);
|
|
if (conversion.Rank == ConversionRank.None)
|
|
{
|
|
return PSConvertBinder.ThrowNoConversion(arg, typeof(int), this, _version);
|
|
}
|
|
|
|
var numericArg = PSConvertBinder.InvokeConverter(conversion, arg.Expression, resultType, debase, ExpressionCache.InvariantCulture);
|
|
|
|
if (typeCode == TypeCode.Decimal || typeCode == TypeCode.Double || typeCode == TypeCode.Single)
|
|
{
|
|
var opType = (typeCode == TypeCode.Decimal) ? typeof(DecimalOps) : typeof(DoubleOps);
|
|
var castType = (typeCode == TypeCode.Decimal) ? typeof(decimal) : typeof(double);
|
|
var methodName = userOp.Substring(3); // Drop the 'op_' prefix to figure out our internal method name.
|
|
return new DynamicMetaObject(
|
|
Expression.Call(opType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static), target.Expression.Cast(castType), numericArg),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var targetExpr = target.Expression.Cast(target.LimitType);
|
|
numericArg = Expression.And(numericArg, Expression.Constant(typeCode < TypeCode.Int64 ? 0x1f : 0x3f, typeof(int)));
|
|
|
|
return new DynamicMetaObject(
|
|
exprGenerator(targetExpr, numericArg).Cast(typeof(object)),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
private DynamicMetaObject LeftShift(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return Shift(target, arg, errorSuggestion, "op_LeftShift", Expression.LeftShift);
|
|
}
|
|
|
|
private DynamicMetaObject RightShift(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return Shift(target, arg, errorSuggestion, "op_RightShift", Expression.RightShift);
|
|
}
|
|
|
|
private DynamicMetaObject BinaryBitwiseXor(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinaryBitwiseOp(target, arg, errorSuggestion, Expression.ExclusiveOr, "op_ExclusiveOr", "-bxor", "BXor");
|
|
}
|
|
|
|
private DynamicMetaObject BinaryBitwiseOr(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinaryBitwiseOp(target, arg, errorSuggestion, Expression.Or, "op_BitwiseOr", "-bor", "BOr");
|
|
}
|
|
|
|
private DynamicMetaObject BinaryBitwiseAnd(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return BinaryBitwiseOp(target, arg, errorSuggestion, Expression.And, "op_BitwiseAnd", "-band", "BAnd");
|
|
}
|
|
|
|
private DynamicMetaObject BinaryBitwiseOp(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion,
|
|
Func<Expression, Expression, Expression> exprGenerator,
|
|
string implicitMethodName,
|
|
string errorOperatorName,
|
|
string methodName)
|
|
{
|
|
if (target.Value == null && arg.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.Constant(0).Cast(typeof(object)), target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var targetUnderlyingType = (target.LimitType.IsEnum) ? Enum.GetUnderlyingType(target.LimitType) : target.LimitType;
|
|
var argUnderlyingType = (arg.LimitType.IsEnum) ? Enum.GetUnderlyingType(arg.LimitType) : arg.LimitType;
|
|
|
|
if (targetUnderlyingType.IsNumericOrPrimitive() || argUnderlyingType.IsNumericOrPrimitive())
|
|
{
|
|
TypeCode leftTypeCode = LanguagePrimitives.GetTypeCode(targetUnderlyingType);
|
|
TypeCode rightTypeCode = LanguagePrimitives.GetTypeCode(argUnderlyingType);
|
|
|
|
Type opType;
|
|
Type opImplType;
|
|
Type toType;
|
|
TypeCode opTypeCode = (int)leftTypeCode >= (int)rightTypeCode ? leftTypeCode : rightTypeCode;
|
|
DynamicMetaObject numericTarget;
|
|
DynamicMetaObject numericArg;
|
|
if (!targetUnderlyingType.IsNumericOrPrimitive())
|
|
{
|
|
opType = GetBitwiseOpType(rightTypeCode);
|
|
numericTarget = GetArgAsNumericOrPrimitive(target, opType);
|
|
numericArg = arg;
|
|
}
|
|
else if (!argUnderlyingType.IsNumericOrPrimitive())
|
|
{
|
|
opType = GetBitwiseOpType(leftTypeCode);
|
|
numericTarget = target;
|
|
numericArg = GetArgAsNumericOrPrimitive(arg, opType);
|
|
}
|
|
else
|
|
{
|
|
numericTarget = target;
|
|
numericArg = arg;
|
|
}
|
|
|
|
if (opTypeCode == TypeCode.Decimal)
|
|
{
|
|
opImplType = typeof(DecimalOps);
|
|
toType = typeof(decimal);
|
|
return new DynamicMetaObject(
|
|
Expression.Call(opImplType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static),
|
|
numericTarget.Expression.Cast(numericTarget.LimitType).Convert(toType),
|
|
numericArg.Expression.Cast(numericArg.LimitType).Convert(toType)),
|
|
numericTarget.CombineRestrictions(numericArg));
|
|
}
|
|
|
|
if (opTypeCode == TypeCode.Double || opTypeCode == TypeCode.Single)
|
|
{
|
|
opImplType = typeof(DoubleOps);
|
|
toType = typeof(double);
|
|
return new DynamicMetaObject(
|
|
Expression.Call(opImplType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static),
|
|
numericTarget.Expression.Cast(numericTarget.LimitType).Convert(toType),
|
|
numericArg.Expression.Cast(numericArg.LimitType).Convert(toType)),
|
|
numericTarget.CombineRestrictions(numericArg));
|
|
}
|
|
|
|
// Figure out the smallest type necessary so we don't lose information.
|
|
// For uint, V2 promoted to long, but it's more correct to use uint.
|
|
// For ulong, V2 incorrectly used long, this is fixed here.
|
|
// For float, double, and decimal operands, we used to use long because V2 did.
|
|
// Because we use unsigned for -bnot, to be consistent, we promote to unsigned here too (-band,-bor,-xor)
|
|
opType = GetBitwiseOpType((int)leftTypeCode >= (int)rightTypeCode ? leftTypeCode : rightTypeCode);
|
|
|
|
if (numericTarget != null && numericArg != null)
|
|
{
|
|
var expr = exprGenerator(numericTarget.Expression.Cast(numericTarget.LimitType).Cast(opType),
|
|
numericArg.Expression.Cast(numericArg.LimitType).Cast(opType));
|
|
|
|
if (target.LimitType.IsEnum)
|
|
{
|
|
expr = expr.Cast(target.LimitType);
|
|
}
|
|
|
|
expr = expr.Cast(typeof(object));
|
|
return new DynamicMetaObject(expr, numericTarget.CombineRestrictions(numericArg));
|
|
}
|
|
}
|
|
|
|
if (target.LimitType == typeof(string) || arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
|
|
return CallImplicitOp(implicitMethodName, target, arg, errorOperatorName, errorSuggestion);
|
|
}
|
|
|
|
#endregion "Arithmetic" operations
|
|
|
|
#region Comparison operations
|
|
|
|
private DynamicMetaObject CompareEQ(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
arg.Value == null ? ExpressionCache.BoxedTrue : ExpressionCache.BoxedFalse,
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && arg.Value == null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
ExpressionCache.BoxedFalse,
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryEqualityComparison(target, arg);
|
|
}
|
|
|
|
private DynamicMetaObject CompareNE(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
arg.Value == null ? ExpressionCache.BoxedFalse : ExpressionCache.BoxedTrue,
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && arg.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.BoxedTrue,
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryEqualityComparison(target, arg);
|
|
}
|
|
|
|
private DynamicMetaObject BinaryEqualityComparison(DynamicMetaObject target, DynamicMetaObject arg)
|
|
{
|
|
var toResult = Operation == ExpressionType.NotEqual ? (Func<Expression, Expression>)Expression.Not : e => e;
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
var targetExpr = target.Expression.Cast(typeof(string));
|
|
|
|
// Doing a string comparison no matter what.
|
|
var argExpr = arg.LimitType != typeof(string)
|
|
? DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string),
|
|
arg.Expression, ExpressionCache.GetExecutionContextFromTLS)
|
|
: arg.Expression.Cast(typeof(string));
|
|
|
|
return new DynamicMetaObject(
|
|
toResult(Compiler.CallStringEquals(targetExpr, argExpr, _ignoreCase)).Cast(typeof(object)),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
if (target.LimitType == typeof(char) && _ignoreCase)
|
|
{
|
|
if (arg.LimitType == typeof(char))
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(
|
|
Operation == ExpressionType.Equal ? CachedReflectionInfo.CharOps_CompareIeq : CachedReflectionInfo.CharOps_CompareIne,
|
|
target.Expression.Cast(typeof(char)),
|
|
arg.Expression.Cast(typeof(char))),
|
|
target.PSGetTypeRestriction().Merge(arg.PSGetTypeRestriction()));
|
|
}
|
|
|
|
if (arg.LimitType == typeof(string))
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(
|
|
Operation == ExpressionType.Equal ? CachedReflectionInfo.CharOps_CompareStringIeq : CachedReflectionInfo.CharOps_CompareStringIne,
|
|
target.Expression.Cast(typeof(char)),
|
|
arg.Expression.Cast(typeof(string))),
|
|
target.PSGetTypeRestriction().Merge(arg.PSGetTypeRestriction()));
|
|
}
|
|
}
|
|
|
|
Expression objectEqualsCall = Expression.Call(target.Expression.Cast(typeof(object)),
|
|
CachedReflectionInfo.Object_Equals,
|
|
arg.Expression.Cast(typeof(object)));
|
|
bool debase;
|
|
var targetType = target.LimitType;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion are protected by the binding rules below.
|
|
var conversion = LanguagePrimitives.FigureConversion(arg.Value, targetType, out debase);
|
|
if (conversion.Rank == ConversionRank.Identity || conversion.Rank == ConversionRank.Assignable
|
|
|| (conversion.Rank == ConversionRank.NullToRef && targetType != typeof(PSReference)))
|
|
{
|
|
// In these cases, no actual conversion is happening, and conversion.Converter will just return
|
|
// the value to be converted. So there is no need to convert the value and compare again.
|
|
return new DynamicMetaObject(toResult(objectEqualsCall).Cast(typeof(object)), target.CombineRestrictions(arg));
|
|
}
|
|
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(arg);
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, targetType, _version));
|
|
|
|
// If there is no conversion, then just rely on 'objectEqualsCall' which most likely will return false. If we attempted the
|
|
// conversion, we'd need extra code to catch an exception we know will happen just to return false.
|
|
if (conversion.Rank == ConversionRank.None)
|
|
{
|
|
return new DynamicMetaObject(toResult(objectEqualsCall).Cast(typeof(object)), bindingRestrictions);
|
|
}
|
|
|
|
// A conversion exists. Generate:
|
|
// tmp = target.Equals(arg)
|
|
// try {
|
|
// if (!tmp) { tmp = target.Equals(Convert(arg, target.GetType())) }
|
|
// } catch (InvalidCastException) { tmp = false }
|
|
// return (operator is -eq/-ceq/-ieq) ? tmp : !tmp
|
|
var resultTmp = Expression.Parameter(typeof(bool));
|
|
|
|
Expression secondEqualsCall =
|
|
Expression.Call(target.Expression.Cast(typeof(object)),
|
|
CachedReflectionInfo.Object_Equals,
|
|
PSConvertBinder.InvokeConverter(conversion, arg.Expression, targetType, debase, ExpressionCache.InvariantCulture).Cast(typeof(object)));
|
|
var expr = Expression.Block(
|
|
new ParameterExpression[] { resultTmp },
|
|
Expression.Assign(resultTmp, objectEqualsCall),
|
|
Expression.IfThen(Expression.Not(resultTmp),
|
|
Expression.TryCatch(Expression.Assign(resultTmp, secondEqualsCall),
|
|
Expression.Catch(typeof(InvalidCastException),
|
|
Expression.Assign(resultTmp, ExpressionCache.Constant(false))))),
|
|
toResult(resultTmp));
|
|
return new DynamicMetaObject(expr.Cast(typeof(object)), bindingRestrictions);
|
|
}
|
|
|
|
private static Expression CompareWithZero(DynamicMetaObject target, Func<Expression, Expression, Expression> comparer)
|
|
{
|
|
return comparer(target.Expression.Cast(target.LimitType), ExpressionCache.Constant(0).Cast(target.LimitType)).Cast(typeof(object));
|
|
}
|
|
|
|
private DynamicMetaObject CompareLT(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && (target.Value == null || arg.Value == null))
|
|
{
|
|
Expression result =
|
|
target.LimitType.IsNumeric() ? CompareWithZero(target, Expression.LessThan)
|
|
: arg.LimitType.IsNumeric() ? CompareWithZero(arg, Expression.GreaterThanOrEqual)
|
|
: arg.Value != null ? ExpressionCache.BoxedTrue
|
|
: ExpressionCache.BoxedFalse;
|
|
|
|
return new DynamicMetaObject(result, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryComparison(target, arg, static e => Expression.LessThan(e, ExpressionCache.Constant(0)));
|
|
}
|
|
|
|
private DynamicMetaObject CompareLE(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && (target.Value == null || arg.Value == null))
|
|
{
|
|
Expression result =
|
|
target.LimitType.IsNumeric() ? CompareWithZero(target, Expression.LessThan)
|
|
: arg.LimitType.IsNumeric() ? CompareWithZero(arg, Expression.GreaterThanOrEqual)
|
|
: target.Value != null ? ExpressionCache.BoxedFalse
|
|
: ExpressionCache.BoxedTrue;
|
|
|
|
return new DynamicMetaObject(result, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryComparison(target, arg, static e => Expression.LessThanOrEqual(e, ExpressionCache.Constant(0)));
|
|
}
|
|
|
|
private DynamicMetaObject CompareGT(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
// Handle a null operand as a special case here unless the target is enumerable or if one of the operands is numeric,
|
|
// in which case null is converted to 0 and regular numeric comparison is done.
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && (target.Value == null || arg.Value == null))
|
|
{
|
|
Expression result =
|
|
target.LimitType.IsNumeric() ? CompareWithZero(target, Expression.GreaterThanOrEqual)
|
|
: arg.LimitType.IsNumeric() ? CompareWithZero(arg, Expression.LessThan)
|
|
: target.Value != null ? ExpressionCache.BoxedTrue
|
|
: ExpressionCache.BoxedFalse;
|
|
|
|
return new DynamicMetaObject(result, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryComparison(target, arg, static e => Expression.GreaterThan(e, ExpressionCache.Constant(0)));
|
|
}
|
|
|
|
private DynamicMetaObject CompareGE(DynamicMetaObject target,
|
|
DynamicMetaObject arg,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
// Handle a null operand as a special case here unless the target is enumerable or if one of the operands is numeric,
|
|
// in which case null is converted to 0 and regular numeric comparison is done.
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable == null && (target.Value == null || arg.Value == null))
|
|
{
|
|
Expression result =
|
|
target.LimitType.IsNumeric() ? CompareWithZero(target, Expression.GreaterThanOrEqual)
|
|
: arg.LimitType.IsNumeric() ? CompareWithZero(arg, Expression.LessThan)
|
|
: arg.Value != null ? ExpressionCache.BoxedFalse
|
|
: ExpressionCache.BoxedTrue;
|
|
|
|
return new DynamicMetaObject(result, target.CombineRestrictions(arg));
|
|
}
|
|
|
|
return BinaryComparisonCommon(enumerable, target, arg)
|
|
?? BinaryComparison(target, arg, static e => Expression.GreaterThanOrEqual(e, ExpressionCache.Constant(0)));
|
|
}
|
|
|
|
private DynamicMetaObject BinaryComparison(DynamicMetaObject target, DynamicMetaObject arg, Func<Expression, Expression> toResult)
|
|
{
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
var targetExpr = target.Expression.Cast(typeof(string));
|
|
|
|
// Doing a string comparison no matter what.
|
|
var argExpr = arg.LimitType != typeof(string)
|
|
? DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string),
|
|
arg.Expression, ExpressionCache.GetExecutionContextFromTLS)
|
|
: arg.Expression.Cast(typeof(string));
|
|
|
|
var expr = Expression.Call(CachedReflectionInfo.StringOps_Compare, targetExpr, argExpr, ExpressionCache.InvariantCulture,
|
|
_ignoreCase
|
|
? ExpressionCache.CompareOptionsIgnoreCase
|
|
: ExpressionCache.CompareOptionsNone);
|
|
|
|
return new DynamicMetaObject(
|
|
toResult(expr).Cast(typeof(object)),
|
|
target.CombineRestrictions(arg));
|
|
}
|
|
|
|
bool debase;
|
|
var targetType = target.LimitType;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion are protected by the binding rules below.
|
|
var conversion = LanguagePrimitives.FigureConversion(arg.Value, targetType, out debase);
|
|
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(arg);
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, targetType, _version));
|
|
|
|
Expression argConverted;
|
|
if (conversion.Rank == ConversionRank.Identity || conversion.Rank == ConversionRank.Assignable)
|
|
{
|
|
argConverted = arg.Expression;
|
|
}
|
|
else if (conversion.Rank == ConversionRank.None)
|
|
{
|
|
// If there is no conversion, then don't bother to invoke the converter. We raise the exception directly.
|
|
var valueToConvert = debase
|
|
? Expression.Call(CachedReflectionInfo.PSObject_Base, arg.Expression)
|
|
: arg.Expression.Cast(typeof(object));
|
|
var errorMsgTuple = Expression.Call(
|
|
CachedReflectionInfo.LanguagePrimitives_GetInvalidCastMessages,
|
|
valueToConvert, Expression.Constant(targetType, typeof(Type)));
|
|
|
|
argConverted = Compiler.ThrowRuntimeError(
|
|
"ComparisonFailure", ExtendedTypeSystem.ComparisonFailure, targetType,
|
|
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), target.Expression, ExpressionCache.GetExecutionContextFromTLS),
|
|
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), arg.Expression, ExpressionCache.GetExecutionContextFromTLS),
|
|
Expression.Property(errorMsgTuple, "Item2"));
|
|
}
|
|
else
|
|
{
|
|
// Invoke the converter. We can raise the exception if the conversion throws InvalidCastException.
|
|
var innerException = Expression.Parameter(typeof(InvalidCastException));
|
|
argConverted =
|
|
Expression.TryCatch(
|
|
PSConvertBinder.InvokeConverter(conversion, arg.Expression, targetType, debase, ExpressionCache.InvariantCulture),
|
|
Expression.Catch(
|
|
innerException,
|
|
Compiler.ThrowRuntimeErrorWithInnerException("ComparisonFailure",
|
|
Expression.Constant(ExtendedTypeSystem.ComparisonFailure), innerException, targetType,
|
|
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), target.Expression, ExpressionCache.GetExecutionContextFromTLS),
|
|
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), arg.Expression, ExpressionCache.GetExecutionContextFromTLS),
|
|
Expression.Property(innerException, CachedReflectionInfo.Exception_Message))));
|
|
}
|
|
|
|
// Prefer IComparable<T> over IComparable if possible
|
|
if (target.LimitType == arg.LimitType)
|
|
{
|
|
foreach (var i in target.Value.GetType().GetInterfaces())
|
|
{
|
|
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IComparable<>))
|
|
{
|
|
return new DynamicMetaObject(
|
|
toResult(Expression.Call(Expression.Convert(target.Expression, i),
|
|
i.GetMethod("CompareTo"),
|
|
argConverted.Cast(arg.LimitType))).Cast(typeof(object)),
|
|
bindingRestrictions);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (target.Value is IComparable)
|
|
{
|
|
return new DynamicMetaObject(
|
|
toResult(Expression.Call(target.Expression.Cast(typeof(IComparable)),
|
|
CachedReflectionInfo.IComparable_CompareTo,
|
|
argConverted.Cast(typeof(object)))).Cast(typeof(object)),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
var throwExpr = Compiler.ThrowRuntimeError("NotIcomparable", ExtendedTypeSystem.NotIcomparable, this.ReturnType, target.Expression);
|
|
|
|
// Try object.Equals. If the objects compare equal, the result is known (true for -ge or -le, false for -gt or -lt), otherwise
|
|
// throw because the objects can't be compared in any meaningful way.
|
|
return new DynamicMetaObject(
|
|
Expression.Condition(
|
|
Expression.Call(target.Expression.Cast(typeof(object)),
|
|
CachedReflectionInfo.Object_Equals,
|
|
arg.Expression.Cast(typeof(object))),
|
|
(Operation == ExpressionType.GreaterThanOrEqual || Operation == ExpressionType.LessThanOrEqual)
|
|
? ExpressionCache.BoxedTrue : ExpressionCache.BoxedFalse,
|
|
throwExpr),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
private DynamicMetaObject BinaryComparisonCommon(DynamicMetaObject targetAsEnumerator, DynamicMetaObject target, DynamicMetaObject arg)
|
|
{
|
|
if (targetAsEnumerator != null && !_scalarCompare)
|
|
{
|
|
// If the target is enumerable, the we generate an object[] result with elements matching.
|
|
// The iteration will be done in a pre-compiled method, but the comparison is done with
|
|
// a dynamically generated lambda that uses a binder
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_Compare,
|
|
targetAsEnumerator.Expression,
|
|
arg.Expression.Cast(typeof(object)),
|
|
Expression.Constant(GetScalarCompareDelegate())),
|
|
targetAsEnumerator.Restrictions.Merge(arg.PSGetTypeRestriction()));
|
|
}
|
|
|
|
if (target.LimitType.IsNumeric())
|
|
{
|
|
var numericArg = GetArgAsNumericOrPrimitive(arg, target.LimitType);
|
|
if (numericArg != null)
|
|
{
|
|
string numericMethod = null;
|
|
switch (Operation)
|
|
{
|
|
case ExpressionType.Equal: numericMethod = "CompareEq"; break;
|
|
case ExpressionType.NotEqual: numericMethod = "CompareNe"; break;
|
|
case ExpressionType.GreaterThan: numericMethod = "CompareGt"; break;
|
|
case ExpressionType.GreaterThanOrEqual: numericMethod = "CompareGe"; break;
|
|
case ExpressionType.LessThan: numericMethod = "CompareLt"; break;
|
|
case ExpressionType.LessThanOrEqual: numericMethod = "CompareLe"; break;
|
|
}
|
|
|
|
return BinaryNumericOp(numericMethod, target, numericArg);
|
|
}
|
|
|
|
if (arg.LimitType == typeof(string))
|
|
{
|
|
return BinaryNumericStringOp(target, arg);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion Comparison operations
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder for unary operators like !, -, or +.
|
|
/// </summary>
|
|
internal sealed class PSUnaryOperationBinder : UnaryOperationBinder
|
|
{
|
|
private static PSUnaryOperationBinder s_notBinder;
|
|
private static PSUnaryOperationBinder s_bnotBinder;
|
|
private static PSUnaryOperationBinder s_unaryMinus;
|
|
private static PSUnaryOperationBinder s_unaryPlusBinder;
|
|
private static PSUnaryOperationBinder s_incrementBinder;
|
|
private static PSUnaryOperationBinder s_decrementBinder;
|
|
|
|
internal static PSUnaryOperationBinder Get(ExpressionType operation)
|
|
{
|
|
switch (operation)
|
|
{
|
|
case ExpressionType.Not:
|
|
if (s_notBinder == null)
|
|
Interlocked.CompareExchange(ref s_notBinder, new PSUnaryOperationBinder(operation), null);
|
|
return s_notBinder;
|
|
case ExpressionType.OnesComplement:
|
|
if (s_bnotBinder == null)
|
|
Interlocked.CompareExchange(ref s_bnotBinder, new PSUnaryOperationBinder(operation), null);
|
|
return s_bnotBinder;
|
|
case ExpressionType.UnaryPlus:
|
|
if (s_unaryPlusBinder == null)
|
|
Interlocked.CompareExchange(ref s_unaryPlusBinder, new PSUnaryOperationBinder(operation), null);
|
|
return s_unaryPlusBinder;
|
|
case ExpressionType.Negate:
|
|
if (s_unaryMinus == null)
|
|
Interlocked.CompareExchange(ref s_unaryMinus, new PSUnaryOperationBinder(operation), null);
|
|
return s_unaryMinus;
|
|
case ExpressionType.Increment:
|
|
if (s_incrementBinder == null)
|
|
Interlocked.CompareExchange(ref s_incrementBinder, new PSUnaryOperationBinder(operation), null);
|
|
return s_incrementBinder;
|
|
case ExpressionType.Decrement:
|
|
if (s_decrementBinder == null)
|
|
Interlocked.CompareExchange(ref s_decrementBinder, new PSUnaryOperationBinder(operation), null);
|
|
return s_decrementBinder;
|
|
}
|
|
|
|
throw new NotImplementedException("Unimplemented unary operation");
|
|
}
|
|
|
|
private PSUnaryOperationBinder(ExpressionType operation) : base(operation)
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackUnaryOperation(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target);
|
|
}
|
|
|
|
switch (Operation)
|
|
{
|
|
case ExpressionType.Not:
|
|
return Not(target, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.OnesComplement:
|
|
return BNot(target, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.UnaryPlus:
|
|
return UnaryPlus(target, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Negate:
|
|
return UnaryMinus(target, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Increment:
|
|
return IncrDecr(target, 1, errorSuggestion).WriteToDebugLog(this);
|
|
case ExpressionType.Decrement:
|
|
return IncrDecr(target, -1, errorSuggestion).WriteToDebugLog(this);
|
|
}
|
|
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "PSUnaryOperationBinder {0}", this.Operation);
|
|
}
|
|
|
|
internal DynamicMetaObject Not(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
// TODO: check op_LogicalNot
|
|
|
|
// This could generate a dynamic site in the expr, which means we might have the same type test twice.
|
|
// We should do better, but this is the simplest implementation, we can add specific cases to handle more common
|
|
// cases if necessary.
|
|
var targetExpr = target.CastOrConvert(typeof(bool));
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Not(targetExpr).Cast(typeof(object)),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
internal DynamicMetaObject BNot(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target);
|
|
}
|
|
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.Constant(-1).Cast(typeof(object)), target.PSGetTypeRestriction());
|
|
}
|
|
|
|
// If the type implements the operator, prefer that.
|
|
var method = target.LimitType.GetMethod("op_OnesComplement", BindingFlags.Static | BindingFlags.Public, null,
|
|
new Type[] { target.LimitType }, null);
|
|
if (method != null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.OnesComplement(target.Expression.Cast(target.LimitType), method).Cast(typeof(object)),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
// Otherwise, do a conversion, as necessary, or throw (and say we can't convert to int, a better message
|
|
// would be nice, but this error is good enough.)
|
|
if (target.LimitType == typeof(string))
|
|
{
|
|
// If we have a string, we defer resolving the operation type until after we know the type of the operand,
|
|
// so generate a dynamic site.
|
|
return new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(this, this.ReturnType,
|
|
PSBinaryOperationBinder.ConvertStringToNumber(target.Expression, typeof(int))),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
Expression targetExpr = null;
|
|
if (!target.LimitType.IsNumeric())
|
|
{
|
|
var resultType = typeof(int);
|
|
bool debase;
|
|
|
|
// ConstrainedLanguage note - calls to this conversion only target numeric types.
|
|
var conversion = LanguagePrimitives.FigureConversion(target.Value, resultType, out debase);
|
|
if (conversion.Rank != ConversionRank.None)
|
|
{
|
|
targetExpr = PSConvertBinder.InvokeConverter(conversion, target.Expression, resultType, debase,
|
|
ExpressionCache.InvariantCulture);
|
|
}
|
|
else
|
|
{
|
|
resultType = typeof(long);
|
|
|
|
// ConstrainedLanguage note - calls to this conversion only target numeric types.
|
|
conversion = LanguagePrimitives.FigureConversion(target.Value, resultType, out debase);
|
|
if (conversion.Rank != ConversionRank.None)
|
|
{
|
|
targetExpr = PSConvertBinder.InvokeConverter(conversion, target.Expression, resultType, debase,
|
|
ExpressionCache.InvariantCulture);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var typeCode = LanguagePrimitives.GetTypeCode(target.LimitType);
|
|
if (typeCode < TypeCode.Int32)
|
|
{
|
|
targetExpr = target.LimitType.IsEnum
|
|
? target.Expression.Cast(Enum.GetUnderlyingType(target.LimitType))
|
|
: target.Expression.Cast(target.LimitType);
|
|
targetExpr = targetExpr.Cast(typeof(int));
|
|
}
|
|
else if (typeCode <= TypeCode.UInt64)
|
|
{
|
|
var targetConvertType = target.LimitType;
|
|
if (targetConvertType.IsEnum)
|
|
{
|
|
targetConvertType = Enum.GetUnderlyingType(targetConvertType);
|
|
}
|
|
|
|
targetExpr = target.Expression.Cast(targetConvertType);
|
|
}
|
|
else
|
|
{
|
|
var opType = (typeCode == TypeCode.Decimal) ? typeof(DecimalOps) : typeof(DoubleOps);
|
|
var castType = (typeCode == TypeCode.Decimal) ? typeof(decimal) : typeof(double);
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(opType.GetMethod("BNot", BindingFlags.Static | BindingFlags.NonPublic), target.Expression.Convert(castType)),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
}
|
|
|
|
if (targetExpr != null)
|
|
{
|
|
Expression result = Expression.OnesComplement(targetExpr);
|
|
if (target.LimitType.IsEnum)
|
|
{
|
|
result = result.Cast(target.LimitType);
|
|
}
|
|
|
|
return new DynamicMetaObject(result.Cast(typeof(object)), target.PSGetTypeRestriction());
|
|
}
|
|
|
|
return errorSuggestion ?? PSConvertBinder.ThrowNoConversion(target, typeof(int), this, -1);
|
|
}
|
|
|
|
private DynamicMetaObject UnaryPlus(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target);
|
|
}
|
|
|
|
if (target.LimitType.IsNumeric())
|
|
{
|
|
var expr = target.Expression.Cast(target.LimitType);
|
|
if (target.LimitType == typeof(byte) || target.LimitType == typeof(sbyte))
|
|
{
|
|
// promote to int, unary plus doesn't support byte directly.
|
|
expr = expr.Cast(typeof(int));
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.UnaryPlus(expr).Cast(typeof(object)),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
// Use a nested dynamic site that adds 0. This won't change the sign, but it will attempt conversions. This is slower than it needs
|
|
// to be, but should be hit rarely.
|
|
return new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(ExpressionType.Add), typeof(object), ExpressionCache.Constant(0), target.Expression),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
private DynamicMetaObject UnaryMinus(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target);
|
|
}
|
|
|
|
if (target.LimitType.IsNumeric())
|
|
{
|
|
var expr = target.Expression.Cast(target.LimitType);
|
|
if (target.LimitType == typeof(byte) || target.LimitType == typeof(sbyte))
|
|
{
|
|
// promote to int, unary plus doesn't support byte directly.
|
|
expr = expr.Cast(typeof(int));
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Negate(expr).Cast(typeof(object)),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
// Use a nested dynamic site that subtracts from 0. This won't change the sign, but it will attempt conversions. This is slower than it needs
|
|
// to be, but should be hit rarely.
|
|
return new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(ExpressionType.Subtract), typeof(object), ExpressionCache.Constant(0), target.Expression),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
private DynamicMetaObject IncrDecr(DynamicMetaObject target, int valueToAdd, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
return this.DeferForPSObject(target);
|
|
}
|
|
|
|
if (target.Value == null)
|
|
{
|
|
return new DynamicMetaObject(ExpressionCache.Constant(valueToAdd).Cast(typeof(object)), target.PSGetTypeRestriction());
|
|
}
|
|
|
|
if (target.LimitType.IsNumeric())
|
|
{
|
|
var arg = new DynamicMetaObject(ExpressionCache.Constant(valueToAdd), BindingRestrictions.Empty, valueToAdd);
|
|
var result = PSBinaryOperationBinder.Get(ExpressionType.Add).FallbackBinaryOperation(target, arg, errorSuggestion);
|
|
return new DynamicMetaObject(
|
|
result.Expression,
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
return errorSuggestion ?? target.ThrowRuntimeError(
|
|
Array.Empty<DynamicMetaObject>(),
|
|
BindingRestrictions.Empty,
|
|
"OperatorRequiresNumber",
|
|
ParserStrings.OperatorRequiresNumber,
|
|
Expression.Constant((Operation == ExpressionType.Increment ? TokenKind.PlusPlus : TokenKind.MinusMinus).Text()),
|
|
Expression.Constant(target.LimitType, typeof(Type)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder for converting a value, e.g. [int]"42"
|
|
/// </summary>
|
|
internal sealed class PSConvertBinder : ConvertBinder
|
|
{
|
|
private static readonly Dictionary<Type, PSConvertBinder> s_binderCache = new Dictionary<Type, PSConvertBinder>();
|
|
internal int _version;
|
|
|
|
public static PSConvertBinder Get(Type type)
|
|
{
|
|
PSConvertBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
if (!s_binderCache.TryGetValue(type, out result))
|
|
{
|
|
result = new PSConvertBinder(type);
|
|
s_binderCache.Add(type, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private PSConvertBinder(Type type)
|
|
: base(type, /*explicit=*/false)
|
|
{
|
|
this._version = 0;
|
|
if (type == typeof(string))
|
|
{
|
|
CacheTarget((Func<CallSite, object, string>)(StringToStringRule));
|
|
}
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value == AutomationNull.Value)
|
|
{
|
|
return new DynamicMetaObject(Expression.Default(this.Type), target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
|
|
bool debase;
|
|
var resultType = this.Type;
|
|
|
|
// ConstrainedLanguage note - this is the main conversion mechanism. If the runspace has ever used
|
|
// ConstrainedLanguage, then start baking in the language mode to the binding rules.
|
|
var conversion = LanguagePrimitives.FigureConversion(target.Value, resultType, out debase);
|
|
|
|
if (errorSuggestion != null && target.Value is DynamicObject)
|
|
{
|
|
return errorSuggestion.WriteToDebugLog(this);
|
|
}
|
|
|
|
BindingRestrictions restrictions = target.PSGetTypeRestriction();
|
|
restrictions = restrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, resultType, _version));
|
|
|
|
return (new DynamicMetaObject(
|
|
InvokeConverter(conversion, target.Expression, resultType, debase, ExpressionCache.InvariantCulture),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "PSConvertBinder [{0}] ver:{1}", Microsoft.PowerShell.ToStringCodeMethods.Type(this.Type, true), _version);
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSConvertBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static DynamicMetaObject ThrowNoConversion(DynamicMetaObject target, Type toType, DynamicMetaObjectBinder binder,
|
|
int currentVersion, params DynamicMetaObject[] args)
|
|
{
|
|
// No conversion, so the result expression raises an error:
|
|
// throw new PSInvalidCastException("ConvertToFinalInvalidCastException", null,
|
|
// ExtendedTypeSystem.InvalidCastException,
|
|
// valueToConvert.ToString(), ObjectToTypeNameString(valueToConvert), resultType.ToString());
|
|
|
|
Expression expr = Expression.Call(CachedReflectionInfo.LanguagePrimitives_ThrowInvalidCastException,
|
|
target.Expression.Cast(typeof(object)),
|
|
Expression.Constant(toType, typeof(Type)));
|
|
|
|
if (binder.ReturnType != typeof(void))
|
|
{
|
|
expr = Expression.Block(expr, Expression.Default(binder.ReturnType));
|
|
}
|
|
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(args);
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(binder, toType, currentVersion));
|
|
|
|
return new DynamicMetaObject(expr, bindingRestrictions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert argument to a ByRef-like type via implicit or explicit conversion.
|
|
/// </summary>
|
|
/// <param name="argument">
|
|
/// The argument to be converted to a ByRef-like type.
|
|
/// </param>
|
|
/// <param name="resultType">
|
|
/// The ByRef-like type to convert to.
|
|
/// </param>
|
|
internal static Expression ConvertToByRefLikeTypeViaCasting(DynamicMetaObject argument, Type resultType)
|
|
{
|
|
var baseObject = PSObject.Base(argument.Value);
|
|
|
|
// Source value cannot be null or AutomationNull, and it cannot be a pure PSObject.
|
|
if (baseObject != null && baseObject is not PSObject)
|
|
{
|
|
Type fromType = baseObject.GetType();
|
|
ConversionRank rank = ConversionRank.None;
|
|
|
|
LanguagePrimitives.FigureCastConversion(fromType, resultType, ref rank);
|
|
if (rank != ConversionRank.None)
|
|
{
|
|
var valueToConvert = baseObject == argument.Value
|
|
? argument.Expression
|
|
: Expression.Call(CachedReflectionInfo.PSObject_Base, argument.Expression);
|
|
|
|
return Expression.Convert(valueToConvert.Cast(fromType), resultType);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static Expression InvokeConverter(LanguagePrimitives.IConversionData conversion,
|
|
Expression value,
|
|
Type resultType,
|
|
bool debase,
|
|
Expression formatProvider)
|
|
{
|
|
Expression conv;
|
|
if (conversion.Rank == ConversionRank.Identity || conversion.Rank == ConversionRank.Assignable)
|
|
{
|
|
conv = debase ? Expression.Call(CachedReflectionInfo.PSObject_Base, value) : value;
|
|
}
|
|
else
|
|
{
|
|
Expression valueToConvert, valueAsPSObject;
|
|
if (debase)
|
|
{
|
|
// Caller has verified the value is a PSObject.
|
|
valueToConvert = Expression.Call(CachedReflectionInfo.PSObject_Base, value);
|
|
valueAsPSObject = value.Cast(typeof(PSObject));
|
|
}
|
|
else
|
|
{
|
|
// Caller has verified the value is not a PSObject, or that PSObject.Base should not be called.
|
|
// If the object is some sort of PSObject, it's most likely a derived to base conversion.
|
|
valueToConvert = value.Cast(typeof(object));
|
|
valueAsPSObject = ExpressionCache.NullPSObject;
|
|
}
|
|
|
|
conv = Expression.Call(
|
|
Expression.Constant(conversion.Converter),
|
|
conversion.Converter.GetType().GetMethod("Invoke"),
|
|
/*valueToConvert=*/ valueToConvert,
|
|
/*resultType=*/ Expression.Constant(resultType, typeof(Type)),
|
|
/*recurse=*/ ExpressionCache.Constant(true),
|
|
/*originalValueToConvert=*/ valueAsPSObject,
|
|
/*formatProvider=*/ formatProvider,
|
|
/*backupTable=*/ ExpressionCache.NullTypeTable);
|
|
}
|
|
|
|
// Skip adding the Convert if unnecessary (same type), or impossible (InternalPSCustomObject)
|
|
if (conv.Type == resultType || resultType == typeof(LanguagePrimitives.InternalPSCustomObject))
|
|
{
|
|
return conv;
|
|
}
|
|
|
|
if (resultType.IsValueType && Nullable.GetUnderlyingType(resultType) == null)
|
|
{
|
|
return Expression.Unbox(conv, resultType);
|
|
}
|
|
|
|
return Expression.Convert(conv, resultType);
|
|
}
|
|
|
|
private static string StringToStringRule(CallSite site, object obj)
|
|
{
|
|
var str = obj as string;
|
|
return str ?? ((CallSite<Func<CallSite, object, string>>)site).Update(site, obj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder to get the value of an indexable object, e.g. $x[1]
|
|
/// </summary>
|
|
internal sealed class PSGetIndexBinder : GetIndexBinder
|
|
{
|
|
private static readonly Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints, bool>, PSGetIndexBinder> s_binderCache
|
|
= new Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints, bool>, PSGetIndexBinder>();
|
|
|
|
private readonly PSMethodInvocationConstraints _constraints;
|
|
private readonly bool _allowSlicing;
|
|
internal int _version;
|
|
|
|
public static PSGetIndexBinder Get(int argCount, PSMethodInvocationConstraints constraints, bool allowSlicing = true)
|
|
{
|
|
lock (s_binderCache)
|
|
{
|
|
PSGetIndexBinder binder;
|
|
var tuple = Tuple.Create(new CallInfo(argCount), constraints, allowSlicing);
|
|
if (!s_binderCache.TryGetValue(tuple, out binder))
|
|
{
|
|
binder = new PSGetIndexBinder(tuple);
|
|
s_binderCache.Add(tuple, binder);
|
|
}
|
|
|
|
return binder;
|
|
}
|
|
}
|
|
|
|
private PSGetIndexBinder(Tuple<CallInfo, PSMethodInvocationConstraints, bool> tuple)
|
|
: base(tuple.Item1)
|
|
{
|
|
_constraints = tuple.Item2;
|
|
_allowSlicing = tuple.Item3;
|
|
this._version = 0;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture,
|
|
"PSGetIndexBinder indexCount={0}{1}{2} ver:{3}",
|
|
this.CallInfo.ArgumentCount,
|
|
_allowSlicing ? string.Empty : " slicing disallowed",
|
|
_constraints == null ? string.Empty : " constraints: " + _constraints,
|
|
_version);
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSGetIndexBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackGetIndex(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || indexes.Any(static mo => !mo.HasValue))
|
|
{
|
|
return Defer(indexes.Prepend(target).ToArray()).WriteToDebugLog(this);
|
|
}
|
|
|
|
if ((target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) ||
|
|
indexes.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value)))
|
|
{
|
|
return this.DeferForPSObject(indexes.Prepend(target).ToArray()).WriteToDebugLog(this);
|
|
}
|
|
|
|
// Check if this is a COM Object
|
|
DynamicMetaObject comResult;
|
|
if (ComInterop.ComBinder.TryBindGetIndex(this, target, indexes, out comResult))
|
|
{
|
|
return comResult.UpdateComRestrictionsForPsObject(indexes).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value == null)
|
|
{
|
|
return (errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, BindingRestrictions.Empty, "NullArray", ParserStrings.NullArray)).WriteToDebugLog(this);
|
|
}
|
|
|
|
// A null index is not allowed unless the index is one of the indices used while slicing, in which case we'll attempt
|
|
// the usual conversions from null to whatever the value being indexed supports.
|
|
// This is oddly inconsistent e.g.:
|
|
// $a[$null] # error
|
|
// $a[$null,$null] # no error, result is an empty array
|
|
// The rationale: V1/V2 did it, and when people are slicing, it's better to return some of the results than none.
|
|
if (indexes.Length == 1 && indexes[0].Value == null && _allowSlicing)
|
|
{
|
|
return (errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, BindingRestrictions.Empty, "NullArrayIndex", ParserStrings.NullArrayIndex)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.LimitType.IsArray)
|
|
{
|
|
return GetIndexArray(target, indexes, errorSuggestion).WriteToDebugLog(this);
|
|
}
|
|
|
|
var defaultMember = target.LimitType.GetCustomAttributes<DefaultMemberAttribute>(true).FirstOrDefault();
|
|
PropertyInfo lengthProperty = null;
|
|
foreach (var i in target.LimitType.GetInterfaces())
|
|
{
|
|
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
|
|
{
|
|
var result = GetIndexDictionary(target, indexes, i);
|
|
if (result != null)
|
|
{
|
|
return result.WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
// If the type explicitly implements an indexer specified by an interface
|
|
// then the DefaultMemberAttribute will not carry over to the implementation.
|
|
// This check will catch those cases.
|
|
if (defaultMember == null)
|
|
{
|
|
defaultMember = i.GetCustomAttributes<DefaultMemberAttribute>(inherit: false).FirstOrDefault();
|
|
if (defaultMember != null)
|
|
{
|
|
lengthProperty = i.GetProperty("Count") ?? i.GetProperty("Length");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (defaultMember != null)
|
|
{
|
|
return InvokeIndexer(target, indexes, errorSuggestion, defaultMember.MemberName, lengthProperty).WriteToDebugLog(this);
|
|
}
|
|
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes).WriteToDebugLog(this);
|
|
}
|
|
|
|
private DynamicMetaObject CannotIndexTarget(DynamicMetaObject target, DynamicMetaObject[] indexes)
|
|
{
|
|
// We want to have
|
|
// $x[0]
|
|
// Be equivalent to
|
|
// $x
|
|
// When $x doesn't have any other way of being indexed. If the index is anything other than 0, we'll
|
|
// throw an error.
|
|
//
|
|
// The motivation for this magic is largely driven by the desire to avoid breaking scripts written around
|
|
// workflows. A workflow that wrote a single object to the pipeline had always returned a collection, so
|
|
// scripts needed to index. This semantic doesn't match the script semantics - a single object written is
|
|
// not wrapped in a collection. The inconsistency had to be addressed, but this code had to be added to
|
|
// avoid breaking partner scripts.
|
|
|
|
// Add version / language checks
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(indexes);
|
|
|
|
// This may be thrown due to a type that was disallowed only due to constrained language.
|
|
// Because of this, we also need a version check
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetVersionCheck(this, _version));
|
|
|
|
// Also add a language mode check to detect toggling between language modes
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
var call = Expression.Call(CachedReflectionInfo.ArrayOps_GetNonIndexable, target.Expression.Cast(typeof(object)),
|
|
Expression.NewArrayInit(typeof(object), indexes.Select(static d => d.Expression.Cast(typeof(object)))));
|
|
return new DynamicMetaObject(call, bindingRestrictions);
|
|
}
|
|
|
|
// Index a generic dictionary via TryGetValue. This routine does not handle slicing,
|
|
// we defer to InvokeIndexer to handle slicing (dictionaries also support general indexing.)
|
|
private DynamicMetaObject GetIndexDictionary(DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
Type idictionary)
|
|
{
|
|
if (indexes.Length > 1)
|
|
{
|
|
// Let InvokeIndexer generate the slicing code, we wouldn't generate anything special here.
|
|
return null;
|
|
}
|
|
|
|
var tryGetValue = idictionary.GetMethod("TryGetValue");
|
|
Diagnostics.Assert(tryGetValue != null, "IDictionary<K,V> has TryGetValue");
|
|
|
|
var parameters = tryGetValue.GetParameters();
|
|
bool debase;
|
|
var keyType = parameters[0].ParameterType;
|
|
|
|
// ConstrainedLanguage note - Calls to this conversion are protected by the binding rules below
|
|
var conversion = LanguagePrimitives.FigureConversion(indexes[0].Value, keyType, out debase);
|
|
if (conversion.Rank == ConversionRank.None)
|
|
{
|
|
// No conversion allows us to call TryGetValue, let InvokeIndexer make the decision (possibly
|
|
// slicing, or possibly just invoke the indexer.
|
|
return null;
|
|
}
|
|
|
|
if (indexes[0].LimitType.IsArray && !keyType.IsArray)
|
|
{
|
|
// There was a conversion, but it's far more likely (and backwards compatible) that we want to do slicing
|
|
return null;
|
|
}
|
|
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(indexes);
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, keyType, _version));
|
|
|
|
var keyExpr = PSConvertBinder.InvokeConverter(conversion, indexes[0].Expression, keyType, debase, ExpressionCache.InvariantCulture);
|
|
var outParam = Expression.Parameter(parameters[1].ParameterType.GetElementType(), "outParam");
|
|
return new DynamicMetaObject(
|
|
Expression.Block(
|
|
new ParameterExpression[] { outParam },
|
|
Expression.Condition(
|
|
Expression.Call(target.Expression.Cast(idictionary), tryGetValue, keyExpr, outParam),
|
|
outParam.Cast(typeof(object)),
|
|
GetNullResult())),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
internal static bool CanIndexFromEndWithNegativeIndex(
|
|
DynamicMetaObject target,
|
|
MethodInfo indexer,
|
|
ParameterInfo[] getterParams)
|
|
{
|
|
// PowerShell supports negative indexing for types that meet the following criteria:
|
|
// - Indexer method accepts one parameter that is typed as int
|
|
// - The int parameter is not a type argument from a constructed generic type
|
|
// (this is to exclude indexers for types that could use a negative index as
|
|
// a valid key like System.Linq.ILookup)
|
|
// - Declares a "Count" or "Length" property
|
|
// - Does not inherit from IDictionary<> as that is handled earlier in the binder
|
|
// For those types, generate special code to check for negative indices, otherwise just generate
|
|
// the call. Before we test for the above criteria explicitly, we will determine if the
|
|
// target is of a type known to be compatible. This is done to avoid the call to Module.ResolveMethod
|
|
// when possible.
|
|
|
|
if (getterParams.Length != 1 || getterParams[0].ParameterType != typeof(int))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Type limitType = target.LimitType;
|
|
if (limitType.IsArray || limitType == typeof(string) || limitType == typeof(StringBuilder))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (typeof(IList).IsAssignableFrom(limitType))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (typeof(OrderedDictionary).IsAssignableFrom(limitType))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// target implements IList<T>?
|
|
if (limitType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Get the base method definition of the indexer to determine if the int
|
|
// parameter is a generic type parameter. Module.ResolveMethod is used
|
|
// because the indexer could be a method from a constructed generic type.
|
|
MethodBase baseMethod = indexer.Module.ResolveMethod(indexer.MetadataToken);
|
|
return !baseMethod.GetParameters()[0].ParameterType.IsGenericParameter;
|
|
}
|
|
|
|
private DynamicMetaObject IndexWithNegativeChecks(
|
|
DynamicMetaObject target,
|
|
DynamicMetaObject index,
|
|
PropertyInfo lengthProperty,
|
|
Func<Expression, Expression, Expression> generateIndexOperation)
|
|
{
|
|
// Generate:
|
|
// try {
|
|
// len = obj.Length
|
|
// if (index < 0)
|
|
// index = index + len
|
|
// obj[index]
|
|
// } catch (Exception e) {
|
|
// if (StrictMode(3)) { throw }
|
|
// $null
|
|
// }
|
|
|
|
var targetTmp = Expression.Parameter(target.LimitType, "target");
|
|
var lenTmp = Expression.Parameter(typeof(int), "len");
|
|
var indexTmp = Expression.Parameter(typeof(int), "index");
|
|
|
|
Expression block = Expression.Block(
|
|
new ParameterExpression[] { targetTmp, lenTmp, indexTmp },
|
|
// Save the target because we use it multiple times.
|
|
Expression.Assign(targetTmp, target.Expression.Cast(target.LimitType)),
|
|
// Save the length because we use it multiple times.
|
|
Expression.Assign(lenTmp,
|
|
Expression.Property(targetTmp, lengthProperty)),
|
|
// Save the index because we use it multiple times
|
|
Expression.Assign(indexTmp, index.Expression),
|
|
// Adjust the index if it's negative
|
|
Expression.IfThen(Expression.LessThan(indexTmp, ExpressionCache.Constant(0)),
|
|
Expression.Assign(indexTmp, Expression.Add(indexTmp, lenTmp))),
|
|
// Generate the index operation
|
|
generateIndexOperation(targetTmp, indexTmp));
|
|
|
|
return new DynamicMetaObject(
|
|
// Do the indexing within a try/catch so we can return $null if the index is out of bounds,
|
|
// or if the index cast fails, e.g. $a = @(1); $a['abc']
|
|
SafeIndexResult(block),
|
|
target.CombineRestrictions(index));
|
|
}
|
|
|
|
private DynamicMetaObject GetIndexArray(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion)
|
|
{
|
|
var array = (Array)target.Value;
|
|
|
|
if (array.Rank > 1)
|
|
{
|
|
return GetIndexMultiDimensionArray(target, indexes, errorSuggestion);
|
|
}
|
|
|
|
if (indexes.Length > 1)
|
|
{
|
|
// If the binder allows slicing, we're definitely slicing, otherwise,
|
|
// calling the indexer will fail because there are either too many or too few indices, so
|
|
// throw an error in that case (and not null.)
|
|
return _allowSlicing
|
|
? InvokeSlicingIndexer(target, indexes)
|
|
: (errorSuggestion ?? CannotIndexTarget(target, indexes));
|
|
}
|
|
|
|
var slicingResult = CheckForSlicing(target, indexes);
|
|
if (slicingResult != null)
|
|
{
|
|
return slicingResult;
|
|
}
|
|
|
|
var indexAsInt = ConvertIndex(indexes[0], typeof(int));
|
|
if (indexAsInt == null)
|
|
{
|
|
// Calling the indexer will fail because we can't convert an index to the correct type.
|
|
return errorSuggestion ?? PSConvertBinder.ThrowNoConversion(target, typeof(int), this, _version, indexes);
|
|
}
|
|
|
|
return IndexWithNegativeChecks(
|
|
new DynamicMetaObject(target.Expression.Cast(target.LimitType), target.PSGetTypeRestriction()),
|
|
new DynamicMetaObject(indexAsInt, indexes[0].PSGetTypeRestriction()),
|
|
target.LimitType.GetProperty("Length"),
|
|
static (t, i) => Expression.ArrayIndex(t, i).Cast(typeof(object)));
|
|
}
|
|
|
|
private DynamicMetaObject GetIndexMultiDimensionArray(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion)
|
|
{
|
|
// We have lots of possibilities here
|
|
//
|
|
// * single index - enumerable, all ints
|
|
// - single result, count must match array rank
|
|
// * single index - enumerable, all enumerable of ints
|
|
// - slicing, falls back to previous case
|
|
// * multiple indices - all ints
|
|
// - single result, must match array rank
|
|
// * multiple indices - enumerable of ints
|
|
// - slicing, falls back to first case
|
|
//
|
|
// In script, the above cases look like:
|
|
// $x = [array]::CreateInstance([int], 3, 3)
|
|
// $y = 0,0
|
|
// $z = $y,$y
|
|
// $x[$y] # case 1
|
|
// $x[$z] # case 2
|
|
// $x[1,1] # case 3
|
|
// $x[(1,1),(0,0)] # case 4
|
|
|
|
var array = (Array)target.Value;
|
|
|
|
if (indexes.Length == 1)
|
|
{
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(indexes[0]);
|
|
if (enumerable == null)
|
|
{
|
|
return target.ThrowRuntimeError(indexes, BindingRestrictions.Empty, "NeedMultidimensionalIndex",
|
|
ParserStrings.NeedMultidimensionalIndex,
|
|
ExpressionCache.Constant(array.Rank),
|
|
DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string),
|
|
indexes[0].Expression, ExpressionCache.GetExecutionContextFromTLS));
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_GetMDArrayValueOrSlice,
|
|
Expression.Convert(target.Expression, typeof(Array)),
|
|
indexes[0].Expression.Cast(typeof(object))),
|
|
target.CombineRestrictions(indexes));
|
|
}
|
|
|
|
var intIndexes = indexes.Select(static index => ConvertIndex(index, typeof(int))).Where(static i => i != null).ToArray();
|
|
if (intIndexes.Length != indexes.Length)
|
|
{
|
|
if (!_allowSlicing)
|
|
{
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes);
|
|
}
|
|
|
|
return InvokeSlicingIndexer(target, indexes);
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_GetMDArrayValue,
|
|
Expression.Convert(target.Expression, typeof(Array)),
|
|
Expression.NewArrayInit(typeof(int), intIndexes),
|
|
ExpressionCache.Constant(!_allowSlicing)),
|
|
target.CombineRestrictions(indexes));
|
|
}
|
|
|
|
private DynamicMetaObject InvokeIndexer(DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
DynamicMetaObject errorSuggestion,
|
|
string methodName,
|
|
PropertyInfo lengthProperty)
|
|
{
|
|
MethodInfo getter = PSInvokeMemberBinder.FindBestMethod(target, indexes, "get_" + methodName, false, _constraints);
|
|
|
|
if (getter == null)
|
|
{
|
|
return CheckForSlicing(target, indexes) ?? errorSuggestion ?? CannotIndexTarget(target, indexes);
|
|
}
|
|
|
|
var getterParams = getter.GetParameters();
|
|
if (getterParams.Length != indexes.Length)
|
|
{
|
|
if (getterParams.Length == 1 && _allowSlicing)
|
|
{
|
|
// We have a slicing operation.
|
|
return InvokeSlicingIndexer(target, indexes);
|
|
}
|
|
|
|
// Calling the indexer will fail because there are either too many or too few indices.
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes);
|
|
}
|
|
|
|
if (getterParams.Length == 1)
|
|
{
|
|
// The getter takes a single argument, so first check if we're slicing.
|
|
var slicingResult = CheckForSlicing(target, indexes);
|
|
if (slicingResult != null)
|
|
{
|
|
return slicingResult;
|
|
}
|
|
}
|
|
|
|
if (getter.ReturnType.IsByRefLike)
|
|
{
|
|
// We cannot return a ByRef-like value in PowerShell, so we disallow getting such an indexer.
|
|
return errorSuggestion ?? new DynamicMetaObject(
|
|
Expression.Block(
|
|
Expression.IfThen(
|
|
Compiler.IsStrictMode(3),
|
|
Compiler.ThrowRuntimeError(
|
|
nameof(ParserStrings.CannotIndexWithByRefLikeReturnType),
|
|
ParserStrings.CannotIndexWithByRefLikeReturnType,
|
|
Expression.Constant(target.LimitType, typeof(Type)),
|
|
Expression.Constant(getter.ReturnType, typeof(Type)))),
|
|
GetNullResult()),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
Expression[] indexExprs = new Expression[getterParams.Length];
|
|
for (int i = 0; i < getterParams.Length; ++i)
|
|
{
|
|
var parameterType = getterParams[i].ParameterType;
|
|
indexExprs[i] = parameterType.IsByRefLike
|
|
? PSConvertBinder.ConvertToByRefLikeTypeViaCasting(indexes[i], parameterType)
|
|
: ConvertIndex(indexes[i], parameterType);
|
|
|
|
if (indexExprs[i] == null)
|
|
{
|
|
// Calling the indexer will fail because we can't convert an index to the correct type.
|
|
return errorSuggestion ?? PSConvertBinder.ThrowNoConversion(target, parameterType, this, _version, indexes);
|
|
}
|
|
}
|
|
|
|
if (CanIndexFromEndWithNegativeIndex(target, getter, getterParams))
|
|
{
|
|
if (lengthProperty == null)
|
|
{
|
|
// Count is declared by most supported types, Length will catch some edge cases like strings.
|
|
lengthProperty = target.LimitType.GetProperty("Count") ??
|
|
target.LimitType.GetProperty("Length");
|
|
}
|
|
|
|
if (lengthProperty != null)
|
|
{
|
|
return IndexWithNegativeChecks(
|
|
new DynamicMetaObject(target.Expression.Cast(target.LimitType),
|
|
target.PSGetTypeRestriction()),
|
|
new DynamicMetaObject(indexExprs[0], indexes[0].PSGetTypeRestriction()),
|
|
lengthProperty,
|
|
(t, i) => Expression.Call(t, getter, i).Cast(typeof(object)));
|
|
}
|
|
}
|
|
|
|
// An indexer may do conversion to an unsafe type, so we need version checks
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(indexes);
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetVersionCheck(this, _version));
|
|
|
|
// Also add a language mode check to detect toggling between language modes
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
return new DynamicMetaObject(
|
|
SafeIndexResult(Expression.Call(target.Expression.Cast(getter.DeclaringType), getter, indexExprs)),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
internal static Expression ConvertIndex(DynamicMetaObject index, Type resultType)
|
|
{
|
|
// ConstrainedLanguage note - Calls to this conversion are protected by the binding rules that call it.
|
|
var conversion = LanguagePrimitives.FigureConversion(index.Value, resultType, out bool debase);
|
|
return conversion.Rank == ConversionRank.None
|
|
? null
|
|
: PSConvertBinder.InvokeConverter(conversion, index.Expression, resultType, debase, ExpressionCache.InvariantCulture);
|
|
}
|
|
|
|
private DynamicMetaObject CheckForSlicing(DynamicMetaObject target, DynamicMetaObject[] indexes)
|
|
{
|
|
if (!_allowSlicing)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (indexes.Length > 1)
|
|
{
|
|
var nonSlicingBinder = PSGetIndexBinder.Get(1, _constraints, allowSlicing: false);
|
|
var expr = Expression.NewArrayInit(typeof(object),
|
|
indexes.Select(i => DynamicExpression.Dynamic(nonSlicingBinder, typeof(object), target.Expression, i.Expression)));
|
|
return new DynamicMetaObject(expr, target.CombineRestrictions(indexes));
|
|
}
|
|
|
|
var enumerableIndex = PSEnumerableBinder.IsEnumerable(indexes[0]);
|
|
if (enumerableIndex != null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_SlicingIndex,
|
|
target.Expression.Cast(typeof(object)),
|
|
enumerableIndex.Expression.Cast(typeof(IEnumerator)),
|
|
Expression.Constant(GetNonSlicingIndexer())),
|
|
target.CombineRestrictions(enumerableIndex));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private DynamicMetaObject InvokeSlicingIndexer(DynamicMetaObject target, DynamicMetaObject[] indexes)
|
|
{
|
|
Diagnostics.Assert(_allowSlicing, "Slicing is not recursive");
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_SlicingIndex,
|
|
target.Expression.Cast(typeof(object)),
|
|
Expression.NewArrayInit(typeof(object),
|
|
indexes.Select(static dmo => dmo.Expression.Cast(typeof(object)))),
|
|
Expression.Constant(GetNonSlicingIndexer())),
|
|
target.CombineRestrictions(indexes));
|
|
}
|
|
|
|
private Expression SafeIndexResult(Expression expr)
|
|
{
|
|
var exception = Expression.Parameter(typeof(Exception));
|
|
return Expression.TryCatch(
|
|
expr.Cast(typeof(object)),
|
|
Expression.Catch(
|
|
exception,
|
|
Expression.Block(
|
|
Expression.IfThen(Compiler.IsStrictMode(3), Expression.Rethrow()),
|
|
GetNullResult())));
|
|
}
|
|
|
|
private Expression GetNullResult()
|
|
{
|
|
return _allowSlicing ? ExpressionCache.NullConstant : ExpressionCache.AutomationNullConstant;
|
|
}
|
|
|
|
private Func<object, object, object> GetNonSlicingIndexer()
|
|
{
|
|
// Rather than cache a single delegate, we create one for each generated rule under the assumption
|
|
// that, although the generated rule may be used in multiple sites, it's better to have
|
|
// multiple delegates (and hence, multiple nested sites) rather than a single nested site for
|
|
// all non-slicing indexing.
|
|
var targetParamExpr = Expression.Parameter(typeof(object));
|
|
var indexParamExpr = Expression.Parameter(typeof(object));
|
|
return Expression.Lambda<Func<object, object, object>>(
|
|
DynamicExpression.Dynamic(PSGetIndexBinder.Get(1, _constraints, allowSlicing: false), typeof(object), targetParamExpr,
|
|
indexParamExpr),
|
|
targetParamExpr, indexParamExpr).Compile();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder for setting the value of an indexable element, like $x[1] = 5.
|
|
/// </summary>
|
|
internal sealed class PSSetIndexBinder : SetIndexBinder
|
|
{
|
|
private static readonly Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints>, PSSetIndexBinder> s_binderCache
|
|
= new Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints>, PSSetIndexBinder>();
|
|
|
|
private readonly PSMethodInvocationConstraints _constraints;
|
|
internal int _version;
|
|
|
|
public static PSSetIndexBinder Get(int argCount, PSMethodInvocationConstraints constraints = null)
|
|
{
|
|
lock (s_binderCache)
|
|
{
|
|
PSSetIndexBinder binder;
|
|
var tuple = Tuple.Create(new CallInfo(argCount), constraints);
|
|
if (!s_binderCache.TryGetValue(tuple, out binder))
|
|
{
|
|
binder = new PSSetIndexBinder(tuple);
|
|
s_binderCache.Add(tuple, binder);
|
|
}
|
|
|
|
return binder;
|
|
}
|
|
}
|
|
|
|
private PSSetIndexBinder(Tuple<CallInfo, PSMethodInvocationConstraints> tuple)
|
|
: base(tuple.Item1)
|
|
{
|
|
_constraints = tuple.Item2;
|
|
this._version = 0;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "PSSetIndexBinder indexCnt={0}{1} ver:{2}",
|
|
CallInfo.ArgumentCount, _constraints == null ? string.Empty : " constraints: " + _constraints, _version);
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSSetIndexBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackSetIndex(
|
|
DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
DynamicMetaObject value,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || indexes.Any(static mo => !mo.HasValue) || !value.HasValue)
|
|
{
|
|
return Defer(indexes.Prepend(target).Append(value).ToArray()).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value) ||
|
|
indexes.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value)))
|
|
{
|
|
return this.DeferForPSObject(indexes.Prepend(target).Append(value).ToArray()).WriteToDebugLog(this);
|
|
}
|
|
|
|
// Check if this is a COM Object
|
|
DynamicMetaObject result;
|
|
if (ComInterop.ComBinder.TryBindSetIndex(this, target, indexes, value, out result))
|
|
{
|
|
return result.UpdateComRestrictionsForPsObject(indexes).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (target.Value == null)
|
|
{
|
|
return (errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, BindingRestrictions.Empty, "NullArray", ParserStrings.NullArray)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (indexes.Length == 1 && indexes[0].Value == null)
|
|
{
|
|
return (errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, BindingRestrictions.Empty, "NullArrayIndex", ParserStrings.NullArrayIndex).WriteToDebugLog(this));
|
|
}
|
|
|
|
if (target.LimitType.IsArray)
|
|
{
|
|
return SetIndexArray(target, indexes, value, errorSuggestion).WriteToDebugLog(this);
|
|
}
|
|
|
|
var defaultMember = target.LimitType.GetCustomAttributes<DefaultMemberAttribute>(true).FirstOrDefault();
|
|
if (defaultMember != null)
|
|
{
|
|
return (InvokeIndexer(target, indexes, value, errorSuggestion, defaultMember.MemberName)).WriteToDebugLog(this);
|
|
}
|
|
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes, value).WriteToDebugLog(this);
|
|
}
|
|
|
|
private DynamicMetaObject CannotIndexTarget(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject value)
|
|
{
|
|
BindingRestrictions bindingRestrictions = value.PSGetTypeRestriction();
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetVersionCheck(this, _version));
|
|
|
|
// Also add a language mode check to detect toggling between language modes
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
return target.ThrowRuntimeError(indexes, bindingRestrictions, "CannotIndex", ParserStrings.CannotIndex, Expression.Constant(target.LimitType, typeof(Type)));
|
|
}
|
|
|
|
private DynamicMetaObject InvokeIndexer(
|
|
DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
DynamicMetaObject value,
|
|
DynamicMetaObject errorSuggestion,
|
|
string methodName)
|
|
{
|
|
MethodInfo setter = PSInvokeMemberBinder.FindBestMethod(target, indexes.Append(value), "set_" + methodName, false, _constraints);
|
|
|
|
if (setter == null)
|
|
{
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes, value);
|
|
}
|
|
|
|
var setterParams = setter.GetParameters();
|
|
int paramLength = setterParams.Length;
|
|
|
|
if (paramLength != indexes.Length + 1)
|
|
{
|
|
// Calling the indexer will fail because there are either too many or too few indices.
|
|
return errorSuggestion ?? CannotIndexTarget(target, indexes, value);
|
|
}
|
|
|
|
if (setterParams[paramLength - 1].ParameterType.IsByRefLike)
|
|
{
|
|
// In theory, it's possible to call the setter with a value that can be implicitly/explicitly casted to the target ByRef-like type.
|
|
// However, the set-property/set-indexer semantics in PowerShell requires returning the value after the setting operation. We cannot
|
|
// return a ByRef-like value back, so we just disallow setting an indexer that takes a ByRef-like type value.
|
|
return errorSuggestion ?? new DynamicMetaObject(
|
|
Compiler.ThrowRuntimeError(
|
|
nameof(ParserStrings.CannotIndexWithByRefLikeReturnType),
|
|
ParserStrings.CannotIndexWithByRefLikeReturnType,
|
|
Expression.Constant(target.LimitType, typeof(Type)),
|
|
Expression.Constant(setterParams[paramLength - 1].ParameterType, typeof(Type))),
|
|
target.PSGetTypeRestriction());
|
|
}
|
|
|
|
Expression[] indexExprs = new Expression[paramLength];
|
|
for (int i = 0; i < paramLength; ++i)
|
|
{
|
|
var parameterType = setterParams[i].ParameterType;
|
|
var argument = (i == paramLength - 1) ? value : indexes[i];
|
|
|
|
indexExprs[i] = parameterType.IsByRefLike
|
|
? PSConvertBinder.ConvertToByRefLikeTypeViaCasting(argument, parameterType)
|
|
: PSGetIndexBinder.ConvertIndex(argument, parameterType);
|
|
|
|
if (indexExprs[i] == null)
|
|
{
|
|
// Calling the indexer will fail because we can't convert an index to the correct type.
|
|
return errorSuggestion ?? PSConvertBinder.ThrowNoConversion(target, parameterType, this, _version, indexes.Append(value).ToArray());
|
|
}
|
|
}
|
|
|
|
if (paramLength == 2
|
|
&& setterParams[0].ParameterType == typeof(int)
|
|
&& target.Value is not IDictionary)
|
|
{
|
|
// PowerShell supports negative indexing for some types (specifically, those with a single
|
|
// int parameter to the indexer, and also have either a Length or Count property.) For
|
|
// those types, generate special code to check for negative indices, otherwise just generate
|
|
// the call.
|
|
PropertyInfo lengthProperty = target.LimitType.GetProperty("Length") ??
|
|
target.LimitType.GetProperty("Count");
|
|
|
|
if (lengthProperty != null)
|
|
{
|
|
return IndexWithNegativeChecks(
|
|
new DynamicMetaObject(target.Expression.Cast(target.LimitType),
|
|
target.PSGetTypeRestriction()),
|
|
new DynamicMetaObject(indexExprs[0], indexes[0].PSGetTypeRestriction()),
|
|
new DynamicMetaObject(indexExprs[1], value.PSGetTypeRestriction()),
|
|
lengthProperty,
|
|
(t, i, v) => Expression.Call(t, setter, i, v));
|
|
}
|
|
}
|
|
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(indexes).Merge(value.PSGetTypeRestriction());
|
|
|
|
// Add the version checks and (potentially) language mode checks, as this setter
|
|
// may invoke a conversion to an unsafe type.
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetVersionCheck(this, _version));
|
|
|
|
// Also add a language mode check to detect toggling between language modes
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
// We'll store the value in a temp so we can return it. We'll also replace the expr in our array of arguments
|
|
// to the indexer with the temp so any conversions are executed just once.
|
|
var valExpr = indexExprs[indexExprs.Length - 1];
|
|
var valTmp = Expression.Parameter(valExpr.Type, "value");
|
|
indexExprs[indexExprs.Length - 1] = valTmp;
|
|
return new DynamicMetaObject(
|
|
Expression.Block(
|
|
new ParameterExpression[] { valTmp },
|
|
Expression.Assign(valTmp, valExpr),
|
|
Expression.Call(target.Expression.Cast(setter.DeclaringType), setter, indexExprs),
|
|
valTmp.Cast(typeof(object))),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
private DynamicMetaObject IndexWithNegativeChecks(
|
|
DynamicMetaObject target,
|
|
DynamicMetaObject index,
|
|
DynamicMetaObject value,
|
|
PropertyInfo lengthProperty,
|
|
Func<Expression, Expression, Expression, Expression> generateIndexOperation)
|
|
{
|
|
BindingRestrictions bindingRestrictions = target.CombineRestrictions(index).Merge(value.Restrictions);
|
|
|
|
// If the target is of an unsafe type for ConstrainedLanguage, we need to pay
|
|
// attention to version and language mode. Otherwise, strongly-typed arrays of unsafe types
|
|
// can be used for type conversion.
|
|
bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, target.LimitType, _version));
|
|
|
|
// Generate:
|
|
// len = obj.Length
|
|
// if (index < 0)
|
|
// index = index + len
|
|
// obj[index] = value
|
|
var targetTmp = Expression.Parameter(target.LimitType, "target");
|
|
var lenTmp = Expression.Parameter(typeof(int), "len");
|
|
var valueExpr = value.Expression;
|
|
var valTmp = Expression.Parameter(valueExpr.Type, "value");
|
|
var indexTmp = Expression.Parameter(typeof(int), "index");
|
|
return new DynamicMetaObject(
|
|
Expression.Block(
|
|
new ParameterExpression[] { targetTmp, valTmp, lenTmp, indexTmp },
|
|
// Save the target because we use it multiple times.
|
|
Expression.Assign(targetTmp, target.Expression.Cast(target.LimitType)),
|
|
// Save the value because it too is used multiple times, but only to keep the DLR happy
|
|
Expression.Assign(valTmp, valueExpr),
|
|
// Save the length because we use it multiple times.
|
|
Expression.Assign(lenTmp,
|
|
Expression.Property(targetTmp, lengthProperty)),
|
|
// Save the index because we use it multiple times
|
|
Expression.Assign(indexTmp, index.Expression),
|
|
// Adjust the index if it's negative
|
|
Expression.IfThen(Expression.LessThan(indexTmp, ExpressionCache.Constant(0)),
|
|
Expression.Assign(indexTmp, Expression.Add(indexTmp, lenTmp))),
|
|
// Do the indexing
|
|
generateIndexOperation(targetTmp, indexTmp, valTmp),
|
|
// Make sure the result of this operation is the value. PowerShell won't use this value
|
|
// in any way, but the DLR requires it (and in theory, if PSObject uses this binder, other
|
|
// languages could use this value.)
|
|
valTmp.Cast(typeof(object))),
|
|
bindingRestrictions);
|
|
}
|
|
|
|
private DynamicMetaObject SetIndexArray(DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
DynamicMetaObject value,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
var array = (Array)target.Value;
|
|
|
|
if (array.Rank > 1)
|
|
{
|
|
return SetIndexMultiDimensionArray(target, indexes, value, errorSuggestion);
|
|
}
|
|
|
|
if (indexes.Length > 1)
|
|
{
|
|
return errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, value.PSGetTypeRestriction(), "ArraySliceAssignmentFailed",
|
|
ParserStrings.ArraySliceAssignmentFailed,
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_IndexStringMessage,
|
|
Expression.NewArrayInit(typeof(object),
|
|
indexes.Select(static i => i.Expression.Cast(typeof(object))))));
|
|
}
|
|
|
|
var intIndex = PSGetIndexBinder.ConvertIndex(indexes[0], typeof(int));
|
|
if (intIndex == null)
|
|
{
|
|
return errorSuggestion ??
|
|
PSConvertBinder.ThrowNoConversion(indexes[0], typeof(int), this, _version, target, value);
|
|
}
|
|
|
|
var elementType = target.LimitType.GetElementType();
|
|
var valueExpr = PSGetIndexBinder.ConvertIndex(value, elementType);
|
|
if (valueExpr == null)
|
|
{
|
|
return errorSuggestion ??
|
|
PSConvertBinder.ThrowNoConversion(value, elementType, this, _version, indexes.Prepend(target).ToArray());
|
|
}
|
|
|
|
return IndexWithNegativeChecks(
|
|
new DynamicMetaObject(target.Expression.Cast(target.LimitType), target.PSGetTypeRestriction()),
|
|
new DynamicMetaObject(intIndex, indexes[0].PSGetTypeRestriction()),
|
|
new DynamicMetaObject(valueExpr, value.PSGetTypeRestriction()), target.LimitType.GetProperty("Length"),
|
|
static (t, i, v) => Expression.Assign(Expression.ArrayAccess(t, i), v));
|
|
}
|
|
|
|
private DynamicMetaObject SetIndexMultiDimensionArray(DynamicMetaObject target,
|
|
DynamicMetaObject[] indexes,
|
|
DynamicMetaObject value,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
var elementType = target.LimitType.GetElementType();
|
|
var valueExpr = PSGetIndexBinder.ConvertIndex(value, elementType);
|
|
if (valueExpr == null)
|
|
{
|
|
return errorSuggestion ??
|
|
PSConvertBinder.ThrowNoConversion(value, elementType, this, _version, indexes.Prepend(target).ToArray());
|
|
}
|
|
|
|
if (indexes.Length == 1)
|
|
{
|
|
var indexExpr = PSGetIndexBinder.ConvertIndex(indexes[0], typeof(int[]));
|
|
if (indexExpr == null)
|
|
{
|
|
return errorSuggestion ??
|
|
PSConvertBinder.ThrowNoConversion(indexes[0], typeof(int[]), this, _version, new DynamicMetaObject[] { target, value });
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_SetMDArrayValue,
|
|
target.Expression.Cast(typeof(Array)),
|
|
indexExpr,
|
|
valueExpr.Cast(typeof(object))),
|
|
target.CombineRestrictions(indexes).Merge(value.PSGetTypeRestriction()));
|
|
}
|
|
|
|
var array = (Array)target.Value;
|
|
|
|
if (indexes.Length != array.Rank)
|
|
{
|
|
return errorSuggestion ??
|
|
target.ThrowRuntimeError(indexes, value.PSGetTypeRestriction(), "NeedMultidimensionalIndex",
|
|
ParserStrings.NeedMultidimensionalIndex,
|
|
ExpressionCache.Constant(array.Rank),
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_IndexStringMessage,
|
|
Expression.NewArrayInit(typeof(object),
|
|
indexes.Select(static i => i.Expression.Cast(typeof(object))))));
|
|
}
|
|
|
|
var indexExprs = new Expression[indexes.Length];
|
|
for (int i = 0; i < indexes.Length; i++)
|
|
{
|
|
indexExprs[i] = PSGetIndexBinder.ConvertIndex(indexes[i], typeof(int));
|
|
if (indexExprs[i] == null)
|
|
{
|
|
return PSConvertBinder.ThrowNoConversion(indexes[i], typeof(int), this, _version,
|
|
indexes.Except(new DynamicMetaObject[] { indexes[i] }).Append(target).Append(value).ToArray());
|
|
}
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.ArrayOps_SetMDArrayValue,
|
|
target.Expression.Cast(typeof(Array)),
|
|
Expression.NewArrayInit(typeof(int), indexExprs),
|
|
valueExpr.Cast(typeof(object))),
|
|
target.CombineRestrictions(indexes).Merge(value.PSGetTypeRestriction()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder for getting a member of a class, like $foo.bar or [foo]::bar.
|
|
/// </summary>
|
|
internal class PSGetMemberBinder : GetMemberBinder
|
|
{
|
|
private sealed class KeyComparer : IEqualityComparer<PSGetMemberBinderKeyType>
|
|
{
|
|
public bool Equals(PSGetMemberBinderKeyType x, PSGetMemberBinderKeyType y)
|
|
{
|
|
// The non-static binder cache is case-sensitive because sites need the name used per site
|
|
// when the target object is a case-sensitive IDictionary. Under all other circumstances,
|
|
// binding is case-insensitive.
|
|
var stringComparison = x.Item3 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
|
return x.Item1.Equals(y.Item1, stringComparison) &&
|
|
x.Item2 == y.Item2 &&
|
|
x.Item3 == y.Item3 &&
|
|
x.Item4 == y.Item4;
|
|
}
|
|
|
|
public int GetHashCode(PSGetMemberBinderKeyType obj)
|
|
{
|
|
var stringComparer = obj.Item3 ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
|
return Utils.CombineHashCodes(stringComparer.GetHashCode(obj.Item1),
|
|
obj.Item2 == null ? 0 : obj.Item2.GetHashCode(),
|
|
obj.Item3.GetHashCode(),
|
|
obj.Item4.GetHashCode());
|
|
}
|
|
}
|
|
|
|
private sealed class ReservedMemberBinder : PSGetMemberBinder
|
|
{
|
|
internal ReservedMemberBinder(string name, bool ignoreCase, bool @static) : base(name, null, ignoreCase, @static, nonEnumerating: false)
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
MethodInfo mi = null;
|
|
Expression targetExpr = null;
|
|
switch (Name)
|
|
{
|
|
case PSObject.AdaptedMemberSetName:
|
|
mi = CachedReflectionInfo.ReservedNameMembers_GeneratePSAdaptedMemberSet;
|
|
targetExpr = target.Expression.Cast(typeof(object));
|
|
break;
|
|
case PSObject.BaseObjectMemberSetName:
|
|
mi = CachedReflectionInfo.ReservedNameMembers_GeneratePSBaseMemberSet;
|
|
targetExpr = target.Expression.Cast(typeof(object));
|
|
break;
|
|
case PSObject.ExtendedMemberSetName:
|
|
mi = CachedReflectionInfo.ReservedNameMembers_GeneratePSExtendedMemberSet;
|
|
targetExpr = target.Expression.Cast(typeof(object));
|
|
break;
|
|
case PSObject.PSObjectMemberSetName:
|
|
mi = CachedReflectionInfo.ReservedNameMembers_GeneratePSObjectMemberSet;
|
|
targetExpr = target.Expression.Cast(typeof(object));
|
|
break;
|
|
case PSObject.PSTypeNames:
|
|
mi = CachedReflectionInfo.ReservedNameMembers_PSTypeNames;
|
|
targetExpr = target.Expression.Convert(typeof(PSObject));
|
|
break;
|
|
}
|
|
|
|
Diagnostics.Assert(mi != null, "ReservedMemberBinder doesn't support member Name");
|
|
|
|
return new DynamicMetaObject(WrapGetMemberInTry(Expression.Call(mi, targetExpr)), target.PSGetTypeRestriction());
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<PSGetMemberBinderKeyType, PSGetMemberBinder> s_binderCache
|
|
= new Dictionary<PSGetMemberBinderKeyType, PSGetMemberBinder>(new KeyComparer());
|
|
|
|
// Because the non-static binder is case-sensitive, we need a list of all binders for a given
|
|
// name when we discover an instance member or type table member for that given name so we
|
|
// can update each of those binders.
|
|
private static readonly ConcurrentDictionary<string, List<PSGetMemberBinder>> s_binderCacheIgnoringCase
|
|
= new ConcurrentDictionary<string, List<PSGetMemberBinder>>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
static PSGetMemberBinder()
|
|
{
|
|
s_binderCache.Add(Tuple.Create(PSObject.AdaptedMemberSetName, (Type)null, false, false),
|
|
new ReservedMemberBinder(PSObject.AdaptedMemberSetName, ignoreCase: true, @static: false));
|
|
s_binderCache.Add(Tuple.Create(PSObject.ExtendedMemberSetName, (Type)null, false, false),
|
|
new ReservedMemberBinder(PSObject.ExtendedMemberSetName, ignoreCase: true, @static: false));
|
|
s_binderCache.Add(Tuple.Create(PSObject.BaseObjectMemberSetName, (Type)null, false, false),
|
|
new ReservedMemberBinder(PSObject.BaseObjectMemberSetName, ignoreCase: true, @static: false));
|
|
s_binderCache.Add(Tuple.Create(PSObject.PSObjectMemberSetName, (Type)null, false, false),
|
|
new ReservedMemberBinder(PSObject.PSObjectMemberSetName, ignoreCase: true, @static: false));
|
|
s_binderCache.Add(Tuple.Create(PSObject.PSTypeNames, (Type)null, false, false),
|
|
new ReservedMemberBinder(PSObject.PSTypeNames, ignoreCase: true, @static: false));
|
|
}
|
|
|
|
private readonly bool _static;
|
|
private readonly bool _nonEnumerating;
|
|
private readonly Type _classScope;
|
|
internal int _version;
|
|
|
|
private bool _hasInstanceMember;
|
|
|
|
internal bool HasInstanceMember { get { return _hasInstanceMember; } }
|
|
|
|
internal static void SetHasInstanceMember(string memberName)
|
|
{
|
|
// We must invalidate dynamic sites (if any) when the first instance member (for this binder)
|
|
// is created, but we don't need to invalidate any sites after the first instance member.
|
|
// Before any instance members exist, restrictions might look like:
|
|
// if (binderVersion == oldBinderVersion && obj is string) { ... }
|
|
// After an instance member is known to exist, the above test (for an object that has no instance
|
|
// member) will look like:
|
|
// MemberInfo mi;
|
|
// if (binderVersion == oldBinderVersion && !TryGetInstanceMember(obj, memberName, out mi) && obj is string)
|
|
// {
|
|
// return ((string)obj).memberName;
|
|
// }
|
|
// else { update site }
|
|
// And if there is an instance member, the generic rule will look like:
|
|
// MemberInfo mi;
|
|
// if (binderVersion == oldBinderVersion && TryGetInstanceMember(obj, memberName, out mi))
|
|
// {
|
|
// return mi.Value;
|
|
// }
|
|
// else { update site }
|
|
// This way, we can avoid the call to TryGetInstanceMember for binders when we know there aren't any instance
|
|
// members, yet invalidate those rules once somebody adds an instance member.
|
|
|
|
var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List<PSGetMemberBinder>());
|
|
|
|
lock (binderList)
|
|
{
|
|
if (binderList.Count == 0)
|
|
{
|
|
// Force one binder to be created if one hasn't been created already.
|
|
PSGetMemberBinder.Get(memberName, (Type)null, @static: false);
|
|
}
|
|
|
|
foreach (var binder in binderList)
|
|
{
|
|
if (!binder._hasInstanceMember)
|
|
{
|
|
lock (binder)
|
|
{
|
|
if (!binder._hasInstanceMember)
|
|
{
|
|
binder._version += 1;
|
|
binder._hasInstanceMember = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool _hasTypeTableMember;
|
|
|
|
internal static void TypeTableMemberAdded(string memberName)
|
|
{
|
|
var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List<PSGetMemberBinder>());
|
|
|
|
lock (binderList)
|
|
{
|
|
if (binderList.Count == 0)
|
|
{
|
|
// Force one binder to be created if one hasn't been created already.
|
|
PSGetMemberBinder.Get(memberName, (Type)null, @static: false);
|
|
}
|
|
|
|
foreach (var binder in binderList)
|
|
{
|
|
lock (binder)
|
|
{
|
|
binder._version += 1;
|
|
binder._hasTypeTableMember = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void TypeTableMemberPossiblyUpdated(string memberName)
|
|
{
|
|
var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List<PSGetMemberBinder>());
|
|
|
|
lock (binderList)
|
|
{
|
|
foreach (var binder in binderList)
|
|
{
|
|
Interlocked.Increment(ref binder._version);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static PSGetMemberBinder Get(string memberName, TypeDefinitionAst classScope, bool @static)
|
|
{
|
|
return Get(memberName, classScope?.Type, @static, false);
|
|
}
|
|
|
|
public static PSGetMemberBinder Get(string memberName, Type classScope, bool @static)
|
|
{
|
|
return Get(memberName, classScope, @static, false);
|
|
}
|
|
|
|
private PSGetMemberBinder GetNonEnumeratingBinder()
|
|
{
|
|
return Get(this.Name, _classScope, @static: false, nonEnumerating: true);
|
|
}
|
|
|
|
private static PSGetMemberBinder Get(string memberName, Type classScope, bool @static, bool nonEnumerating)
|
|
{
|
|
PSGetMemberBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
var tuple = Tuple.Create(memberName, classScope, @static, nonEnumerating);
|
|
if (!s_binderCache.TryGetValue(tuple, out result))
|
|
{
|
|
// We might be seeing a reserved name with a different case. Check for that before
|
|
// creating a new binder. For reserved names, we can safely use a single binder for
|
|
// any case.
|
|
if (PSMemberInfoCollection<PSMemberInfo>.IsReservedName(memberName))
|
|
{
|
|
var tupleLower = Tuple.Create(memberName.ToLowerInvariant(), (Type)null, @static, nonEnumerating);
|
|
result = s_binderCache[tupleLower];
|
|
}
|
|
else
|
|
{
|
|
result = new PSGetMemberBinder(memberName, classScope, true, @static, nonEnumerating);
|
|
if (!@static)
|
|
{
|
|
var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List<PSGetMemberBinder>());
|
|
lock (binderList)
|
|
{
|
|
if (binderList.Count > 0)
|
|
{
|
|
result._hasInstanceMember = binderList[0]._hasInstanceMember;
|
|
result._hasTypeTableMember = binderList[0]._hasTypeTableMember;
|
|
}
|
|
|
|
binderList.Add(result);
|
|
|
|
Diagnostics.Assert(binderList.All(b => b._hasInstanceMember == result._hasInstanceMember),
|
|
"All binders in the list should have _hasInstanceMember set identically");
|
|
Diagnostics.Assert(binderList.All(b => b._hasTypeTableMember == result._hasTypeTableMember),
|
|
"All binders in the list should have _hasTypeTableMember set identically");
|
|
}
|
|
}
|
|
}
|
|
|
|
s_binderCache.Add(tuple, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private PSGetMemberBinder(string name, Type classScope, bool ignoreCase, bool @static, bool nonEnumerating)
|
|
: base(name, ignoreCase)
|
|
{
|
|
_static = @static;
|
|
_classScope = classScope;
|
|
this._version = 0;
|
|
_nonEnumerating = nonEnumerating;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "GetMember: {0}{1}{2} ver:{3}",
|
|
Name, _static ? " static" : string.Empty, _nonEnumerating ? " nonEnumerating" : string.Empty, _version);
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue)
|
|
{
|
|
return Defer(target);
|
|
}
|
|
|
|
// Defer COM objects or arguments wrapped in PSObjects
|
|
if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))
|
|
{
|
|
object baseObject = PSObject.Base(target.Value);
|
|
if (baseObject != null && Marshal.IsComObject(baseObject))
|
|
{
|
|
// We unwrap only if the 'base' is a COM object. It's unnecessary to unwrap in other cases,
|
|
// especially in the case of strings, we would lose instance members on the PSObject.
|
|
// Therefore, we need to use a stricter restriction to make sure PSObject 'target' with other
|
|
// base types doesn't get unwrapped.
|
|
return this.DeferForPSObject(target, targetIsComObject: true).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
// Check if this is a COM Object
|
|
DynamicMetaObject result;
|
|
if (ComInterop.ComBinder.TryBindGetMember(this, target, out result, delayInvocation: false))
|
|
{
|
|
result = new DynamicMetaObject(WrapGetMemberInTry(result.Expression), result.Restrictions);
|
|
return result.WriteToDebugLog(this);
|
|
}
|
|
|
|
object targetValue = PSObject.Base(target.Value);
|
|
|
|
if (targetValue == null)
|
|
{
|
|
// PSGetTypeRestriction will actually create an instance restriction because the targetValue is null.
|
|
return PropertyDoesntExist(target, target.PSGetTypeRestriction()).WriteToDebugLog(this);
|
|
}
|
|
|
|
BindingRestrictions restrictions;
|
|
PSMemberInfo memberInfo;
|
|
Expression expr = null;
|
|
if (_hasInstanceMember && TryGetInstanceMember(target.Value, Name, out memberInfo))
|
|
{
|
|
// If there is an instance member, we generate (roughly) the following:
|
|
// PSMemberInfo memberInfo;
|
|
// if (PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
|
|
// return memberInfo.Value;
|
|
// else
|
|
// update the site
|
|
// We use a generic method like this because:
|
|
// * If one object has an instance property with a given name, it's like many others do as well
|
|
// * We want to avoid generating new sites for every object with an instance member
|
|
// As an alternative, would could generate the following psuedo-code:
|
|
// if (target.Value == previousInstance)
|
|
// return optimized value (depending on the exact PSMemberInfo subclass)
|
|
// else update the site
|
|
// But the assumption here is that many sites probably performs worse than the dictionary lookup
|
|
// and unoptimized virtual call to PSMemberInfo.Value.
|
|
//
|
|
// The binding restrictions could avoid a version check because it's never wrong to look for an instance member,
|
|
// but we add the check because the DLR requires a non-empty check when the target implements IDynamicMetaObjectProvider,
|
|
// which PSObject does. The version check is also marginally useful if we knew we'd never see another
|
|
// instance member with this member name, but we're not tracking things to make that a useful test.
|
|
|
|
var memberInfoVar = Expression.Variable(typeof(PSMemberInfo));
|
|
expr = Expression.Condition(
|
|
Expression.Call(CachedReflectionInfo.PSGetMemberBinder_TryGetInstanceMember, target.Expression.Cast(typeof(object)), Expression.Constant(Name), memberInfoVar),
|
|
Expression.Property(memberInfoVar, "Value"),
|
|
this.GetUpdateExpression(typeof(object)));
|
|
expr = WrapGetMemberInTry(expr);
|
|
|
|
return (new DynamicMetaObject(Expression.Block(new[] { memberInfoVar }, expr), BinderUtils.GetVersionCheck(this, _version))).WriteToDebugLog(this);
|
|
}
|
|
|
|
bool canOptimize;
|
|
Type aliasConversionType;
|
|
memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property);
|
|
|
|
if (!canOptimize)
|
|
{
|
|
Diagnostics.Assert(memberInfo == null, "We don't bother returning members if we can't optimize.");
|
|
return new DynamicMetaObject(
|
|
WrapGetMemberInTry(Expression.Call(CachedReflectionInfo.PSGetMemberBinder_GetAdaptedValue,
|
|
GetTargetExpr(target, typeof(object)),
|
|
Expression.Constant(Name))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (memberInfo != null)
|
|
{
|
|
Diagnostics.Assert(memberInfo.instance == null, "We shouldn't be here if a member is already bound.");
|
|
|
|
// The most common case - we're getting some property. We can optimize many different kinds
|
|
// of property accessors, so we special case each possibility.
|
|
var propertyInfo = memberInfo as PSPropertyInfo;
|
|
if (propertyInfo != null)
|
|
{
|
|
if (!propertyInfo.IsGettable)
|
|
{
|
|
return GenerateGetPropertyException(restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var property = propertyInfo as PSProperty;
|
|
if (property != null)
|
|
{
|
|
var adapterData = property.adapterData as DotNetAdapter.PropertyCacheEntry;
|
|
Diagnostics.Assert(adapterData != null, "We have an unknown PSProperty that we aren't correctly optimizing.");
|
|
|
|
if (adapterData.member.DeclaringType.IsGenericTypeDefinition || adapterData.propertyType.IsByRefLike)
|
|
{
|
|
// We really should throw an error, but accessing property getter
|
|
// doesn't throw error in PowerShell since V2, even in strict mode.
|
|
expr = ExpressionCache.NullConstant;
|
|
}
|
|
else
|
|
{
|
|
// For static property access, the target expr must be null. For non-static, we must convert
|
|
// because target.Expression is typeof(object) because this is a dynamic site.
|
|
var targetExpr = _static ? null : GetTargetExpr(target, adapterData.member.DeclaringType);
|
|
var propertyAccessor = adapterData.member as PropertyInfo;
|
|
if (propertyAccessor != null)
|
|
{
|
|
if (propertyAccessor.GetMethod.IsFamily &&
|
|
(_classScope == null || !_classScope.IsSubclassOf(propertyAccessor.DeclaringType)))
|
|
{
|
|
return GenerateGetPropertyException(restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
expr = Expression.Property(targetExpr, propertyAccessor);
|
|
}
|
|
else
|
|
{
|
|
Diagnostics.Assert(adapterData.member is FieldInfo,
|
|
"A DotNetAdapter.PropertyCacheEntry has something other than PropertyInfo or FieldInfo.");
|
|
expr = Expression.Field(targetExpr, (FieldInfo)adapterData.member);
|
|
}
|
|
}
|
|
}
|
|
|
|
var scriptProperty = propertyInfo as PSScriptProperty;
|
|
if (scriptProperty != null)
|
|
{
|
|
expr = Expression.Call(Expression.Constant(scriptProperty, typeof(PSScriptProperty)),
|
|
CachedReflectionInfo.PSScriptProperty_InvokeGetter, target.Expression.Cast(typeof(object)));
|
|
}
|
|
|
|
var codeProperty = propertyInfo as PSCodeProperty;
|
|
if (codeProperty != null)
|
|
{
|
|
Diagnostics.Assert(codeProperty.GetterCodeReference != null, "CodeProperty isn't gettable, should have generated error code above.");
|
|
Diagnostics.Assert(codeProperty.GetterCodeReference.IsStatic, "CodeProperty should be a static method.");
|
|
|
|
expr = PSInvokeMemberBinder.InvokeMethod(codeProperty.GetterCodeReference, null, new[] { target },
|
|
false, PSInvokeMemberBinder.MethodInvocationType.Getter);
|
|
}
|
|
|
|
var noteProperty = propertyInfo as PSNoteProperty;
|
|
if (noteProperty != null)
|
|
{
|
|
Diagnostics.Assert(!noteProperty.IsSettable, "If the note is settable, incorrect code is generated.");
|
|
expr = Expression.Property(Expression.Constant(propertyInfo, typeof(PSNoteProperty)), CachedReflectionInfo.PSNoteProperty_Value);
|
|
}
|
|
|
|
Diagnostics.Assert(expr != null, "Unexpected property type encountered");
|
|
|
|
if (aliasConversionType != null)
|
|
{
|
|
expr = expr.Convert(aliasConversionType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
expr = Expression.Call(CachedReflectionInfo.PSGetMemberBinder_CloneMemberInfo,
|
|
Expression.Constant(memberInfo, typeof(PSMemberInfo)),
|
|
target.Expression.Cast(typeof(object)));
|
|
}
|
|
}
|
|
|
|
if (targetValue is IDictionary)
|
|
{
|
|
Type genericTypeArg = null;
|
|
bool isGeneric = IsGenericDictionary(targetValue, ref genericTypeArg);
|
|
|
|
if (!isGeneric || genericTypeArg != null)
|
|
{
|
|
var temp = Expression.Variable(typeof(object));
|
|
if (expr == null)
|
|
{
|
|
// If expr is not null, it's the fallback when no member exists. If it is null,
|
|
// the fallback is the result from PropertyDoesntExist.
|
|
|
|
expr = (errorSuggestion ?? PropertyDoesntExist(target, restrictions)).Expression;
|
|
}
|
|
|
|
var method = isGeneric
|
|
? CachedReflectionInfo.PSGetMemberBinder_TryGetGenericDictionaryValue.MakeGenericMethod(genericTypeArg)
|
|
: CachedReflectionInfo.PSGetMemberBinder_TryGetIDictionaryValue;
|
|
expr = Expression.Block(new[] { temp },
|
|
Expression.Condition(
|
|
Expression.Call(method, GetTargetExpr(target, method.GetParameters()[0].ParameterType), Expression.Constant(Name), temp),
|
|
temp,
|
|
expr.Cast(typeof(object))));
|
|
}
|
|
}
|
|
|
|
return expr != null
|
|
? new DynamicMetaObject(WrapGetMemberInTry(expr), restrictions).WriteToDebugLog(this)
|
|
: (errorSuggestion ?? PropertyDoesntExist(target, restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
private DynamicMetaObject GenerateGetPropertyException(BindingRestrictions restrictions)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Compiler.ThrowRuntimeError("WriteOnlyProperty", ExtendedTypeSystem.WriteOnlyProperty,
|
|
this.ReturnType, Expression.Constant(Name)),
|
|
restrictions);
|
|
}
|
|
|
|
internal static bool IsGenericDictionary(object value, ref Type genericTypeArg)
|
|
{
|
|
bool isGeneric = false;
|
|
foreach (var i in value.GetType().GetInterfaces())
|
|
{
|
|
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
|
|
{
|
|
isGeneric = true;
|
|
var genericArguments = i.GetGenericArguments();
|
|
if (genericArguments[0] == typeof(string))
|
|
{
|
|
// Our generic method for lookup takes IDictionary<string,T>, we need
|
|
// to remember T.
|
|
genericTypeArg = genericArguments[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
return isGeneric;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the actual value, as an expression, of the object represented by target. This
|
|
/// will get the base object if it's a psobject, plus correctly handle Nullable.
|
|
/// </summary>
|
|
internal static Expression GetTargetExpr(DynamicMetaObject target, Type castToType = null)
|
|
{
|
|
var expr = target.Expression;
|
|
var value = target.Value;
|
|
|
|
// If the target value is actually a deserialized PSObject, we should use the original value
|
|
var psobj = value as PSObject;
|
|
if (psobj != null && psobj != AutomationNull.Value && !psobj.IsDeserialized)
|
|
{
|
|
expr = Expression.Call(CachedReflectionInfo.PSObject_Base, expr);
|
|
value = PSObject.Base(value);
|
|
}
|
|
|
|
var type = castToType ?? ((value != null) ? value.GetType() : typeof(object));
|
|
|
|
// Assemblies in CoreCLR might not allow reflection execution on their internal types. In such case, we walk up
|
|
// the derivation chain to find the first public parent, and use reflection methods on the public parent.
|
|
if (!TypeResolver.IsPublic(type) && DotNetAdapter.DisallowPrivateReflection(type))
|
|
{
|
|
var publicType = DotNetAdapter.GetFirstPublicParentType(type);
|
|
if (publicType != null)
|
|
{
|
|
type = publicType;
|
|
}
|
|
// else we'll probably fail, but the error message might be more helpful than NullReferenceException
|
|
}
|
|
|
|
if (expr.Type != type)
|
|
{
|
|
// Unbox value types (or use Nullable<T>.Value) to avoid a copy in case the value is mutated.
|
|
// In case that castToType is System.Object and expr.Type is Nullable<ValueType>, expr.Cast(System.Object) will
|
|
// get the underlying value by default. So "GetTargetExpr(target).Cast(typeof(object))" is actually the same as
|
|
// "GetTargetExpr(target, typeof(object))".
|
|
expr = type.IsValueType
|
|
? (Nullable.GetUnderlyingType(expr.Type) != null
|
|
? (Expression)Expression.Property(expr, "Value")
|
|
: Expression.Unbox(expr, type))
|
|
: expr.Cast(type);
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the binding result when no property exists.
|
|
/// </summary>
|
|
private DynamicMetaObject PropertyDoesntExist(DynamicMetaObject target, BindingRestrictions restrictions)
|
|
{
|
|
// If the property does not exist, but the target is enumerable, we'll turn this expression into roughly the equivalent
|
|
// pipeline:
|
|
// $x | foreach-object { $_.Property }
|
|
// I say roughly because we'll actually iterate through $_ if it doesn't have Property and it is enumerable, and we'll
|
|
// do this recursively. This makes it easy to chain property references and not worry if the property returns collections or not, e.g.:
|
|
// $x.Modules.ModuleName
|
|
// If Modules returns a collection, but you want all the module names of all the modules, then it just works.
|
|
// The _nonEnumerating aspect of this binder is simply a way of avoiding the recursing inside the binder, allowing us to
|
|
// collect the results in the helper method we call. One alternative to _nonEnumerating is to have the helper method
|
|
// not recurse, but mark it's return value specially so that recursive calls to the helper can detect that the results
|
|
// need to be flattened.
|
|
// IsEnumerable treats AutomationNull.Value as a zero length array which we don't want to do here.
|
|
if (!_nonEnumerating && target.Value != AutomationNull.Value)
|
|
{
|
|
var enumerable = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerable != null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_PropertyGetter,
|
|
Expression.Constant(this.GetNonEnumeratingBinder()),
|
|
enumerable.Expression), restrictions);
|
|
}
|
|
}
|
|
|
|
// As part of our effort to hide how a command can return a singleton or array, we want to allow people to iterate
|
|
// over singletons with the foreach statement (which has worked since V1) and a for loop, for example:
|
|
// for ($i = 0; $i -lt $x.Length; $i++) { $x[$i] }
|
|
// If $x is a singleton, we want to return 1 for the length so code like this works correctly.
|
|
// We do not want this magic to show up in Get-Member output, tab completion, intellisense, etc.
|
|
if (Name.Equals("Length", StringComparison.OrdinalIgnoreCase) || Name.Equals("Count", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// $null.Count should be 0, anything else should be 1
|
|
var resultCount = PSObject.Base(target.Value) == null ? 0 : 1;
|
|
return new DynamicMetaObject(
|
|
Expression.Condition(
|
|
Compiler.IsStrictMode(2),
|
|
ThrowPropertyNotFoundStrict(),
|
|
ExpressionCache.Constant(resultCount).Cast(typeof(object))), restrictions);
|
|
}
|
|
|
|
var result = Expression.Condition(
|
|
Compiler.IsStrictMode(2),
|
|
ThrowPropertyNotFoundStrict(),
|
|
_nonEnumerating ? ExpressionCache.AutomationNullConstant : ExpressionCache.NullConstant);
|
|
return new DynamicMetaObject(result, restrictions);
|
|
}
|
|
|
|
private Expression ThrowPropertyNotFoundStrict()
|
|
{
|
|
return Compiler.CreateThrow(typeof(object), typeof(PropertyNotFoundException),
|
|
new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
|
|
"PropertyNotFoundStrict", null, ParserStrings.PropertyNotFoundStrict,
|
|
new object[] { Name });
|
|
}
|
|
|
|
internal static DynamicMetaObject EnsureAllowedInLanguageMode(ExecutionContext context, DynamicMetaObject target, object targetValue,
|
|
string name, bool isStatic, DynamicMetaObject[] args, BindingRestrictions moreTests, string errorID, string resourceString)
|
|
{
|
|
if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage)
|
|
{
|
|
if (!IsAllowedInConstrainedLanguage(targetValue, name, isStatic))
|
|
{
|
|
return target.ThrowRuntimeError(args, moreTests, errorID, resourceString);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static bool IsAllowedInConstrainedLanguage(object targetValue, string name, bool isStatic)
|
|
{
|
|
// ToString allowed on any type
|
|
if (string.Equals(name, "ToString", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, check if it's a core type
|
|
Type targetType = targetValue as Type;
|
|
if ((!isStatic) || (targetType == null))
|
|
{
|
|
targetType = targetValue.GetType();
|
|
}
|
|
|
|
return CoreTypes.Contains(targetType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the binding restriction that tests that an instance member does not exist, used when the binder
|
|
/// knows instance members might exist (because the name was added to some instance), but the object we're
|
|
/// currently binding does not have an instance member with the given member name.
|
|
/// </summary>
|
|
internal BindingRestrictions NotInstanceMember(DynamicMetaObject target)
|
|
{
|
|
var memberInfoVar = Expression.Variable(typeof(PSMemberInfo));
|
|
var expr = Expression.Call(CachedReflectionInfo.PSGetMemberBinder_TryGetInstanceMember,
|
|
target.Expression.Cast(typeof(object)), Expression.Constant(Name), memberInfoVar);
|
|
|
|
return BindingRestrictions.GetExpressionRestriction(Expression.Block(new[] { memberInfoVar }, Expression.Not(expr)));
|
|
}
|
|
|
|
private static Expression WrapGetMemberInTry(Expression expr)
|
|
{
|
|
// This code ensures that getting a member doesn't raise an exception. Mostly this is so that formatting
|
|
// always works. As currently implemented, this will also affect C# code that uses the dynamic keyword.
|
|
// If we decide that the dynamic keyword should not mask exceptions, then we should create a new binder
|
|
// from PSObject.PSDynamicMetaObject.BindGetMember that passes in a flag so we know not to wrap in a try/catch.
|
|
|
|
return Expression.TryCatch(
|
|
expr.Cast(typeof(object)),
|
|
Expression.Catch(typeof(TerminateException), Expression.Rethrow(typeof(object))),
|
|
// Not sure if the following catch is necessary, but the interpreter has it.
|
|
Expression.Catch(typeof(MethodException), Expression.Rethrow(typeof(object))),
|
|
// This catch is only needed if we have an IDictionary
|
|
Expression.Catch(typeof(PropertyNotFoundException), Expression.Rethrow(typeof(object))),
|
|
Expression.Catch(typeof(Exception), ExpressionCache.NullConstant));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolve the alias, throwing an exception if a cycle is detected while resolving the alias.
|
|
/// </summary>
|
|
private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject target, HashSet<string> aliases,
|
|
List<BindingRestrictions> aliasRestrictions)
|
|
{
|
|
Diagnostics.Assert(aliasRestrictions != null, "aliasRestrictions cannot be null");
|
|
if (aliases == null)
|
|
{
|
|
aliases = new HashSet<string> { alias.Name };
|
|
}
|
|
else
|
|
{
|
|
if (aliases.Contains(alias.Name))
|
|
{
|
|
throw new ExtendedTypeSystemException("CycleInAliasLookup", null, ExtendedTypeSystem.CycleInAlias, alias.Name);
|
|
}
|
|
|
|
aliases.Add(alias.Name);
|
|
}
|
|
|
|
bool canOptimize;
|
|
Type aliasConversionType;
|
|
BindingRestrictions restrictions;
|
|
PSGetMemberBinder binder = PSGetMemberBinder.Get(alias.ReferencedMemberName, _classScope, false);
|
|
// if binder has instance member, then GetPSMemberInfo will not be able to resolve that..only FallbackGetMember
|
|
// can resolve that. In that case we simply return without further evaluation.
|
|
if (binder.HasInstanceMember)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType,
|
|
MemberTypes.Property, aliases, aliasRestrictions);
|
|
return result;
|
|
}
|
|
|
|
internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
|
|
out BindingRestrictions restrictions,
|
|
out bool canOptimize,
|
|
out Type aliasConversionType,
|
|
MemberTypes memberTypeToOperateOn,
|
|
HashSet<string> aliases = null,
|
|
List<BindingRestrictions> aliasRestrictions = null)
|
|
{
|
|
aliasConversionType = null;
|
|
bool hasTypeTableMember;
|
|
bool hasInstanceMember;
|
|
BindingRestrictions versionRestriction;
|
|
lock (this)
|
|
{
|
|
versionRestriction = BinderUtils.GetVersionCheck(this, _version);
|
|
hasTypeTableMember = _hasTypeTableMember;
|
|
hasInstanceMember = _hasInstanceMember;
|
|
}
|
|
|
|
if (_static)
|
|
{
|
|
restrictions = target.PSGetStaticMemberRestriction();
|
|
restrictions = restrictions.Merge(versionRestriction);
|
|
canOptimize = true;
|
|
|
|
return PSObject.GetStaticCLRMember(target.Value, Name);
|
|
}
|
|
|
|
canOptimize = false;
|
|
|
|
PSMemberInfo unused;
|
|
Diagnostics.Assert(!TryGetInstanceMember(target.Value, Name, out unused),
|
|
"shouldn't get here if there is an instance member");
|
|
|
|
PSMemberInfo memberInfo = null;
|
|
ConsolidatedString typenames = null;
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
var typeTable = context?.TypeTable;
|
|
|
|
if (hasTypeTableMember)
|
|
{
|
|
typenames = PSObject.GetTypeNames(target.Value);
|
|
if (typeTable != null)
|
|
{
|
|
memberInfo = typeTable.GetMembers<PSMemberInfo>(typenames)[Name];
|
|
if (memberInfo != null)
|
|
{
|
|
canOptimize = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the target value is actually a deserialized PSObject.
|
|
// - If so, we want to use the original value.
|
|
// Mostly, a deserialized object is a PSObject with an empty immediate base object, and it's OK to call PSObject.Base()
|
|
// on it in this case, because the method would just return the original PSObject. But if it's the deserialized object of
|
|
// a container object (i.e. an object derived from IEnumerable, IList, or IDictionary), the immediate base object is a
|
|
// Hashtable or ArrayList. In such case, we sometimes would lose the psadapted/psextended properties that we actually care
|
|
// by using the base object.
|
|
//
|
|
// One example is the XmlElement, which derives from IEnumerable. It is serialized/deserialized as a container object, and
|
|
// the its element properties (i.e. $xmlElement.IP, where IP is actually an attribute name) are stored as psadapted properties
|
|
// in the top-level PSObject.
|
|
//
|
|
// See the comments about 'three interesting cases' in PSInvokeMemberBinder.FallbackInvokeMember for more info.
|
|
//
|
|
// - If not, we want to use the base object, so that we might generate optimized code.
|
|
var psobj = target.Value as PSObject;
|
|
bool isTargetDeserializedObject = (psobj != null) && (psobj.IsDeserialized);
|
|
object value = isTargetDeserializedObject ? target.Value : PSObject.Base(target.Value);
|
|
|
|
var adapterSet = PSObject.GetMappedAdapter(value, typeTable);
|
|
if (memberInfo == null)
|
|
{
|
|
canOptimize = adapterSet.OriginalAdapter.CanSiteBinderOptimize(memberTypeToOperateOn);
|
|
// Don't bother looking for the member if we're not going to use it.
|
|
if (canOptimize)
|
|
{
|
|
memberInfo = adapterSet.OriginalAdapter.BaseGetMember<PSMemberInfo>(value, Name);
|
|
}
|
|
}
|
|
|
|
if (memberInfo == null && canOptimize && adapterSet.DotNetAdapter != null)
|
|
{
|
|
memberInfo = adapterSet.DotNetAdapter.BaseGetMember<PSMemberInfo>(value, Name);
|
|
}
|
|
|
|
// The member came from the type table or an adapter and isn't instance based, so the restriction will start
|
|
// with a version check
|
|
restrictions = versionRestriction;
|
|
|
|
// When returning aliasRestrictions always include the version restriction
|
|
if (aliasRestrictions != null)
|
|
{
|
|
aliasRestrictions.Add(versionRestriction);
|
|
}
|
|
|
|
var alias = memberInfo as PSAliasProperty;
|
|
if (alias != null)
|
|
{
|
|
aliasConversionType = alias.ConversionType;
|
|
if (aliasRestrictions == null)
|
|
{
|
|
aliasRestrictions = new List<BindingRestrictions>();
|
|
}
|
|
|
|
memberInfo = ResolveAlias(alias, target, aliases, aliasRestrictions);
|
|
if (memberInfo == null)
|
|
{
|
|
// this can happen in the cases where referenced name of the alias property
|
|
// maps to an adapter that cannot optimize (like ManagementObjectAdapter)
|
|
canOptimize = false;
|
|
}
|
|
|
|
// Merge alias restrictions
|
|
foreach (var aliasRestriction in aliasRestrictions)
|
|
{
|
|
restrictions = restrictions.Merge(aliasRestriction);
|
|
}
|
|
}
|
|
|
|
if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.DotNetInstanceAdapter)
|
|
{
|
|
List<MethodBase> candidateMethods = null;
|
|
foreach (var member in _classScope.GetMembers(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic))
|
|
{
|
|
if (this.Name.Equals(member.Name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var propertyInfo = member as PropertyInfo;
|
|
if (propertyInfo != null)
|
|
{
|
|
var getMethod = propertyInfo.GetGetMethod(nonPublic: true);
|
|
var setMethod = propertyInfo.GetSetMethod(nonPublic: true);
|
|
|
|
if ((getMethod == null || getMethod.IsFamily || getMethod.IsPublic) &&
|
|
(setMethod == null || setMethod.IsFamily || setMethod.IsPublic))
|
|
{
|
|
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var fieldInfo = member as FieldInfo;
|
|
if (fieldInfo != null)
|
|
{
|
|
if (fieldInfo.IsFamily)
|
|
{
|
|
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var methodInfo = member as MethodInfo;
|
|
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily))
|
|
{
|
|
if (candidateMethods == null)
|
|
{
|
|
candidateMethods = new List<MethodBase>();
|
|
}
|
|
|
|
candidateMethods.Add(methodInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (candidateMethods != null && candidateMethods.Count > 0)
|
|
{
|
|
var psMethodInfo = memberInfo as PSMethod;
|
|
if (psMethodInfo != null)
|
|
{
|
|
var cacheEntry = (DotNetAdapter.MethodCacheEntry)psMethodInfo.adapterData;
|
|
candidateMethods.AddRange(cacheEntry.methodInformationStructures.Select(static e => e.method));
|
|
memberInfo = null;
|
|
}
|
|
|
|
if (memberInfo != null)
|
|
{
|
|
// Ambiguous, it'd be better to report an error other than "can't find member", but I'm lazy.
|
|
memberInfo = null;
|
|
}
|
|
else
|
|
{
|
|
DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods.ToArray());
|
|
memberInfo = PSMethod.Create(this.Name, PSObject.DotNetInstanceAdapter, null, method);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasInstanceMember)
|
|
{
|
|
// If this binder knows instance members exist, we need to make sure future objects going through this
|
|
// rule ensure they don't have an instance member. I don't expect this rule to be generated or hit frequently.
|
|
restrictions = restrictions.Merge(NotInstanceMember(target));
|
|
}
|
|
|
|
// We always need a type check, even if we'll be using the PSTypeNames because our generated code may contain
|
|
// conversions that don't work for arbitrary types.
|
|
restrictions = restrictions.Merge(target.PSGetTypeRestriction());
|
|
|
|
// If the target value is actually a deserialized PSObject, add a check to ensure that's the case. This check
|
|
// should be done after the type check.
|
|
if (isTargetDeserializedObject)
|
|
{
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized)));
|
|
}
|
|
|
|
if (hasTypeTableMember)
|
|
{
|
|
// We need to make sure the type table we would use to find a member is the same type table that we used here to
|
|
// find (or not find) a member. If they were different type tables, we could easily get different results.
|
|
restrictions = restrictions.Merge(
|
|
BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSGetMemberBinder_GetTypeTableFromTLS), typeTable));
|
|
|
|
// We also need to make sure the pstypename is the same. It doesn't matter if we found something in the type table
|
|
// or not - the fact that we might find something in the type table is enough to require a check on the pstypename.
|
|
restrictions = restrictions.Merge(
|
|
BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.PSGetMemberBinder_IsTypeNameSame, target.Expression.Cast(typeof(object)), Expression.Constant(typenames.Key))));
|
|
}
|
|
|
|
return memberInfo;
|
|
}
|
|
|
|
#region Runtime helper methods
|
|
|
|
internal static PSMemberInfo CloneMemberInfo(PSMemberInfo memberInfo, object obj)
|
|
{
|
|
memberInfo = memberInfo.Copy();
|
|
memberInfo.ReplicateInstance(obj);
|
|
return memberInfo;
|
|
}
|
|
|
|
internal static object GetAdaptedValue(object obj, string member)
|
|
{
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
PSMemberInfo memberInfo = null;
|
|
|
|
if ((context != null) && (context.TypeTable != null))
|
|
{
|
|
ConsolidatedString typenames = PSObject.GetTypeNames(obj);
|
|
memberInfo = context.TypeTable.GetMembers<PSMemberInfo>(typenames)[member];
|
|
if (memberInfo != null)
|
|
{
|
|
memberInfo = CloneMemberInfo(memberInfo, obj);
|
|
}
|
|
}
|
|
|
|
var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable);
|
|
if (memberInfo == null)
|
|
{
|
|
memberInfo = adapterSet.OriginalAdapter.BaseGetMember<PSMemberInfo>(obj, member);
|
|
}
|
|
|
|
if (memberInfo == null && adapterSet.DotNetAdapter != null)
|
|
{
|
|
memberInfo = adapterSet.DotNetAdapter.BaseGetMember<PSMemberInfo>(obj, member);
|
|
}
|
|
|
|
if (memberInfo != null)
|
|
{
|
|
return memberInfo.Value;
|
|
}
|
|
|
|
if (string.Equals(member, "Length", StringComparison.OrdinalIgnoreCase) || string.Equals(member, "Count", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (context != null && context.IsStrictVersion(2))
|
|
{
|
|
// If the member is undefined and we're in strict mode, throw an exception...
|
|
throw new PropertyNotFoundException("PropertyNotFoundStrict", null, ParserStrings.PropertyNotFoundStrict,
|
|
LanguagePrimitives.ConvertTo<string>(member));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static bool IsTypeNameSame(object value, string typeName)
|
|
{
|
|
return value != null && string.Equals(PSObject.GetTypeNames(value).Key, typeName, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
internal static TypeTable GetTypeTableFromTLS()
|
|
{
|
|
var executionContext = LocalPipeline.GetExecutionContextFromTLS();
|
|
return executionContext?.TypeTable;
|
|
}
|
|
|
|
internal static bool TryGetInstanceMember(object value, string memberName, out PSMemberInfo memberInfo)
|
|
{
|
|
PSMemberInfoInternalCollection<PSMemberInfo> instanceMembers;
|
|
memberInfo = PSObject.HasInstanceMembers(value, out instanceMembers) ? instanceMembers[memberName] : null;
|
|
|
|
return (memberInfo != null);
|
|
}
|
|
|
|
internal static bool TryGetIDictionaryValue(IDictionary hash, string memberName, out object value)
|
|
{
|
|
try
|
|
{
|
|
if (hash.Contains(memberName))
|
|
{
|
|
value = hash[memberName];
|
|
return true;
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Ignore invalid operation exception, it can happen if the dictionary
|
|
// has keys that can't be compared to property.
|
|
}
|
|
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
internal static bool TryGetGenericDictionaryValue<T>(IDictionary<string, T> hash, string memberName, out object value)
|
|
{
|
|
T result;
|
|
if (hash.TryGetValue(memberName, out result))
|
|
{
|
|
value = result;
|
|
return true;
|
|
}
|
|
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
#endregion Runtime helper methods
|
|
}
|
|
|
|
/// <summary>
|
|
/// The binder for setting a member, like $foo.bar = 1 or [foo]::bar = 1.
|
|
/// </summary>
|
|
internal class PSSetMemberBinder : SetMemberBinder
|
|
{
|
|
private sealed class KeyComparer : IEqualityComparer<PSSetMemberBinderKeyType>
|
|
{
|
|
public bool Equals(PSSetMemberBinderKeyType x, PSSetMemberBinderKeyType y)
|
|
{
|
|
// The non-static binder cache is case-sensitive because sites need the name used per site
|
|
// when the target object is a case-sensitive IDictionary. Under all other circumstances,
|
|
// binding is case-insensitive.
|
|
var stringComparison = x.Item3 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
|
return x.Item1.Equals(y.Item1, stringComparison) &&
|
|
x.Item2 == y.Item2 &&
|
|
x.Item3 == y.Item3;
|
|
}
|
|
|
|
public int GetHashCode(PSSetMemberBinderKeyType obj)
|
|
{
|
|
var stringComparer = obj.Item3 ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
|
return Utils.CombineHashCodes(stringComparer.GetHashCode(obj.Item1),
|
|
obj.Item2 == null ? 0 : obj.Item2.GetHashCode(),
|
|
obj.Item3.GetHashCode());
|
|
}
|
|
}
|
|
|
|
private static readonly Dictionary<PSSetMemberBinderKeyType, PSSetMemberBinder> s_binderCache
|
|
= new Dictionary<PSSetMemberBinderKeyType, PSSetMemberBinder>(new KeyComparer());
|
|
|
|
private readonly bool _static;
|
|
private readonly Type _classScope;
|
|
private readonly PSGetMemberBinder _getMemberBinder;
|
|
|
|
public static PSSetMemberBinder Get(string memberName, TypeDefinitionAst classScopeAst, bool @static)
|
|
{
|
|
var classScope = classScopeAst?.Type;
|
|
return Get(memberName, classScope, @static);
|
|
}
|
|
|
|
public static PSSetMemberBinder Get(string memberName, Type classScope, bool @static)
|
|
{
|
|
PSSetMemberBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
var tuple = Tuple.Create(memberName, classScope, @static);
|
|
if (!s_binderCache.TryGetValue(tuple, out result))
|
|
{
|
|
result = new PSSetMemberBinder(memberName, true, @static, classScope);
|
|
s_binderCache.Add(tuple, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public PSSetMemberBinder(string name, bool ignoreCase, bool @static, Type classScope)
|
|
: base(name, ignoreCase)
|
|
{
|
|
_static = @static;
|
|
_classScope = classScope;
|
|
_getMemberBinder = PSGetMemberBinder.Get(name, _classScope, @static);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture, "SetMember: {0}{1} ver:{2}", _static ? "static " : string.Empty, Name, _getMemberBinder._version);
|
|
}
|
|
|
|
private static Expression GetTransformedExpression(IEnumerable<ArgumentTransformationAttribute> transformationAttributes, Expression originalExpression)
|
|
{
|
|
if (transformationAttributes == null)
|
|
{
|
|
return originalExpression;
|
|
}
|
|
|
|
var attributesArray = transformationAttributes.ToArray();
|
|
if (attributesArray.Length == 0)
|
|
{
|
|
return originalExpression;
|
|
}
|
|
|
|
Expression transformedExpression = originalExpression.Convert(typeof(object));
|
|
var engineIntrinsicsTempVar = Expression.Variable(typeof(EngineIntrinsics));
|
|
// apply transformation attributes from right to left
|
|
for (int i = attributesArray.Length - 1; i >= 0; i--)
|
|
{
|
|
transformedExpression = Expression.Call(Expression.Constant(attributesArray[i]),
|
|
CachedReflectionInfo.ArgumentTransformationAttribute_Transform,
|
|
engineIntrinsicsTempVar,
|
|
transformedExpression);
|
|
}
|
|
|
|
return Expression.Block(new[] { engineIntrinsicsTempVar },
|
|
Expression.Assign(
|
|
engineIntrinsicsTempVar,
|
|
Expression.Property(ExpressionCache.GetExecutionContextFromTLS,
|
|
CachedReflectionInfo.ExecutionContext_EngineIntrinsics)),
|
|
transformedExpression);
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, DynamicMetaObject value, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || !value.HasValue)
|
|
{
|
|
return Defer(target, value);
|
|
}
|
|
|
|
// Defer COM objects or arguments wrapped in PSObjects
|
|
if ((target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) ||
|
|
(value.Value is PSObject && (PSObject.Base(value.Value) != value.Value)))
|
|
{
|
|
object baseObject = PSObject.Base(target.Value);
|
|
if (baseObject != null && Marshal.IsComObject(baseObject))
|
|
{
|
|
// We unwrap only if the 'base' of 'target' is a COM object. It's unnecessary to unwrap in other cases,
|
|
// especially in the case that 'target' is a string, we would lose instance members on the PSObject.
|
|
// Therefore, we need to use a stricter restriction to make sure PSObject 'target' with other base types
|
|
// doesn't get unwrapped.
|
|
return this.DeferForPSObject(target, value, targetIsComObject: true).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
// Check if this is a COM Object
|
|
DynamicMetaObject result;
|
|
if (ComInterop.ComBinder.TryBindSetMember(this, target, value, out result))
|
|
{
|
|
return result.UpdateComRestrictionsForPsObject(new DynamicMetaObject[] { value }).WriteToDebugLog(this);
|
|
}
|
|
|
|
var targetValue = PSObject.Base(target.Value);
|
|
if (targetValue == null)
|
|
{
|
|
return (target.ThrowRuntimeError(new[] { value }, BindingRestrictions.Empty, "PropertyNotFound",
|
|
ParserStrings.PropertyNotFound,
|
|
Expression.Constant(Name))).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (value.Value == AutomationNull.Value)
|
|
{
|
|
// Pretend the value was null (so we actually use null as the expression, but be sure
|
|
// to use a restriction that checks
|
|
value = new DynamicMetaObject(ExpressionCache.NullConstant, value.PSGetTypeRestriction(), null);
|
|
}
|
|
|
|
PSMemberInfo memberInfo;
|
|
if (_getMemberBinder.HasInstanceMember && PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
|
|
{
|
|
// If there is an instance member, we generate (roughly) the following:
|
|
// PSMemberInfo memberInfo;
|
|
// if (PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
|
|
// memberInfo.Value = value;
|
|
// else
|
|
// update the site
|
|
// We use a generic method like this because:
|
|
// * If one object has an instance property with a given name, it's like many others do as well
|
|
// * We want to avoid generating new sites for every object with an instance member
|
|
// As an alternative, would could generate the following psuedo-code:
|
|
// if (target.Value == previousInstance)
|
|
// return optimized value (depending on the exact PSMemberInfo subclass)
|
|
// else update the site
|
|
// But the assumption here is that many sites probably performs worse than the dictionary lookup
|
|
// and unoptimized virtual call to PSMemberInfo.Value.
|
|
//
|
|
// The binding restrictions could avoid a version check because it's never wrong to look for an instance member,
|
|
// but we add the check because the DLR requires a non-empty check when the target implements IDynamicMetaObjectProvider,
|
|
// which PSObject does. The version check is also marginally useful if we knew we'd never see another
|
|
// instance member with this member name, but we're not tracking things to make that a useful test.
|
|
|
|
var memberInfoVar = Expression.Variable(typeof(PSMemberInfo));
|
|
var temp = Expression.Variable(typeof(object));
|
|
var expr = Expression.Condition(
|
|
Expression.Call(CachedReflectionInfo.PSGetMemberBinder_TryGetInstanceMember,
|
|
target.Expression.Cast(typeof(object)), Expression.Constant(Name), memberInfoVar),
|
|
Expression.Assign(Expression.Property(memberInfoVar, "Value"), value.Expression.Cast(typeof(object))),
|
|
this.GetUpdateExpression(typeof(object)));
|
|
var bindingRestrictions = BinderUtils.GetVersionCheck(_getMemberBinder, _getMemberBinder._version)
|
|
.Merge(value.PSGetTypeRestriction());
|
|
|
|
return (new DynamicMetaObject(Expression.Block(new[] { memberInfoVar, temp }, expr),
|
|
bindingRestrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (targetValue is IDictionary)
|
|
{
|
|
// We never look for properties in the underlying object, we always try to add the key.
|
|
Type genericTypeArg = null;
|
|
bool isGeneric = PSGetMemberBinder.IsGenericDictionary(targetValue, ref genericTypeArg);
|
|
|
|
if (!isGeneric || genericTypeArg != null)
|
|
{
|
|
// If it's a generic, we must convert our value to genericTypeArg.
|
|
var hashType = isGeneric
|
|
? typeof(IDictionary<,>).MakeGenericType(typeof(string), genericTypeArg)
|
|
: typeof(IDictionary);
|
|
var mi = hashType.GetMethod("set_Item");
|
|
|
|
var temp = Expression.Variable(genericTypeArg ?? typeof(object));
|
|
|
|
bool debase;
|
|
Type elementType = temp.Type;
|
|
|
|
// ConstrainedLanguage note - Calls to this conversion are protected by the binding rules below
|
|
var conversion = LanguagePrimitives.FigureConversion(value.Value, elementType, out debase);
|
|
if (conversion.Rank != ConversionRank.None)
|
|
{
|
|
var valueExpr = PSConvertBinder.InvokeConverter(conversion, value.Expression, elementType,
|
|
debase, ExpressionCache.InvariantCulture);
|
|
return new DynamicMetaObject(
|
|
Expression.Block(new[] { temp },
|
|
Expression.Assign(temp, valueExpr),
|
|
Expression.Call(PSGetMemberBinder.GetTargetExpr(target, hashType), mi,
|
|
Expression.Constant(Name), valueExpr),
|
|
valueExpr.Cast(typeof(object))),
|
|
target.CombineRestrictions(value)).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
BindingRestrictions restrictions;
|
|
bool canOptimize;
|
|
Type aliasConversionType;
|
|
memberInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property);
|
|
|
|
restrictions = restrictions.Merge(value.PSGetTypeRestriction());
|
|
|
|
// If the process has ever used ConstrainedLanguage, then we need to add the language mode
|
|
// to the binding restrictions, and check whether it is allowed. We can't limit
|
|
// the language check to unsafe types, as a safe type might have an unsafe method.
|
|
if (ExecutionContext.HasEverUsedConstrainedLanguage)
|
|
{
|
|
restrictions = restrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
// Validate that this is allowed in the current language mode
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode(
|
|
context, target, targetValue, Name, _static, new[] { value }, restrictions,
|
|
"PropertySetterNotSupportedInConstrainedLanguage", ParserStrings.PropertySetConstrainedLanguage);
|
|
if (runtimeError != null)
|
|
{
|
|
return runtimeError.WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
if (!canOptimize)
|
|
{
|
|
Diagnostics.Assert(memberInfo == null, "We don't bother returning members if we can't optimize.");
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.PSSetMemberBinder_SetAdaptedValue,
|
|
PSGetMemberBinder.GetTargetExpr(target, typeof(object)),
|
|
Expression.Constant(Name),
|
|
value.Expression.Cast(typeof(object))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (memberInfo == null)
|
|
{
|
|
return (errorSuggestion ?? new DynamicMetaObject(
|
|
Compiler.ThrowRuntimeError("PropertyAssignmentException", ParserStrings.PropertyNotFound, this.ReturnType, Expression.Constant(Name)),
|
|
restrictions)).WriteToDebugLog(this);
|
|
}
|
|
|
|
var psPropertyInfo = memberInfo as PSPropertyInfo;
|
|
if (psPropertyInfo != null)
|
|
{
|
|
if (!psPropertyInfo.IsSettable)
|
|
{
|
|
return GeneratePropertyAssignmentException(restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var psProperty = psPropertyInfo as PSProperty;
|
|
if (psProperty != null)
|
|
{
|
|
var data = psProperty.adapterData as DotNetAdapter.PropertyCacheEntry;
|
|
if (data != null)
|
|
{
|
|
Expression expr;
|
|
|
|
if (data.member.DeclaringType.IsGenericTypeDefinition)
|
|
{
|
|
Expression innerException = Expression.New(
|
|
CachedReflectionInfo.SetValueException_ctor,
|
|
Expression.Constant("PropertyAssignmentException"),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.CannotInvokeStaticMethodOnUninstantiatedGenericType),
|
|
Expression.NewArrayInit(typeof(object), Expression.Constant(data.member.DeclaringType.FullName)));
|
|
|
|
expr = Compiler.ThrowRuntimeErrorWithInnerException(
|
|
"PropertyAssignmentException",
|
|
Expression.Constant(ExtendedTypeSystem.CannotInvokeStaticMethodOnUninstantiatedGenericType),
|
|
innerException,
|
|
this.ReturnType,
|
|
Expression.Constant(data.member.DeclaringType.FullName));
|
|
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (data.propertyType.IsByRefLike)
|
|
{
|
|
// In theory, it's possible to call the setter with a value that can be implicitly/explicitly casted to the target ByRef-like type.
|
|
// However, the set-property/set-indexer semantics in PowerShell requires returning the value after the setting operation. We cannot
|
|
// return a ByRef-like value back, so we just disallow setting a member that takes a ByRef-like type value.
|
|
expr = Expression.Throw(
|
|
Expression.New(
|
|
CachedReflectionInfo.SetValueException_ctor,
|
|
Expression.Constant(nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField)),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField),
|
|
Expression.NewArrayInit(
|
|
typeof(object),
|
|
Expression.Constant(data.member.Name),
|
|
Expression.Constant(data.propertyType, typeof(Type)))),
|
|
this.ReturnType);
|
|
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var propertyInfo = data.member as PropertyInfo;
|
|
Expression lhs;
|
|
Type lhsType;
|
|
|
|
// Populate transformation attributes.
|
|
// Order of attributes is the same as order provided by user in the code
|
|
// We assume that GetCustomAttributes implemented that way.
|
|
IEnumerable<ArgumentTransformationAttribute> argumentTransformationAttributes =
|
|
data.member.GetCustomAttributes<ArgumentTransformationAttribute>();
|
|
bool transformationNeeded = argumentTransformationAttributes.Any();
|
|
|
|
// For static property access, the target expr must be null. For non-static, we must convert
|
|
// because target.Expression is typeof(object) because this is a dynamic site.
|
|
var targetExpr = _static ? null : PSGetMemberBinder.GetTargetExpr(target, data.member.DeclaringType);
|
|
if (propertyInfo != null)
|
|
{
|
|
if (propertyInfo.SetMethod.IsFamily &&
|
|
(_classScope == null || !_classScope.IsSubclassOf(propertyInfo.DeclaringType)))
|
|
{
|
|
return GeneratePropertyAssignmentException(restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
lhsType = propertyInfo.PropertyType;
|
|
lhs = Expression.Property(targetExpr, propertyInfo);
|
|
}
|
|
else
|
|
{
|
|
Diagnostics.Assert(data.member is FieldInfo,
|
|
"A DotNetAdapter.PropertyCacheEntry has something other than PropertyInfo or FieldInfo.");
|
|
var fieldInfo = (FieldInfo)data.member;
|
|
lhsType = fieldInfo.FieldType;
|
|
lhs = Expression.Field(targetExpr, fieldInfo);
|
|
}
|
|
|
|
var nullableUnderlyingType = Nullable.GetUnderlyingType(lhsType);
|
|
if (nullableUnderlyingType != null)
|
|
{
|
|
if (value.Value == null)
|
|
{
|
|
expr = Expression.Block(
|
|
Expression.Assign(lhs, GetTransformedExpression(argumentTransformationAttributes, Expression.Constant(null, lhsType))),
|
|
ExpressionCache.NullConstant);
|
|
}
|
|
else
|
|
{
|
|
var tmp = Expression.Variable(nullableUnderlyingType);
|
|
Expression assignmentExpression;
|
|
if (transformationNeeded)
|
|
{
|
|
var transformedExpr = GetTransformedExpression(argumentTransformationAttributes, value.Expression);
|
|
assignmentExpression = DynamicExpression.Dynamic(PSConvertBinder.Get(nullableUnderlyingType), nullableUnderlyingType, transformedExpr);
|
|
}
|
|
else
|
|
{
|
|
assignmentExpression = value.CastOrConvert(nullableUnderlyingType);
|
|
}
|
|
|
|
expr = Expression.Block(
|
|
new[] { tmp },
|
|
Expression.Assign(tmp, assignmentExpression),
|
|
Expression.Assign(lhs, Expression.New(lhsType.GetConstructor(new[] { nullableUnderlyingType }), tmp)),
|
|
tmp.Cast(typeof(object)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var tmp = Expression.Variable(lhsType);
|
|
Expression assignedValue;
|
|
if (transformationNeeded)
|
|
{
|
|
assignedValue = DynamicExpression.Dynamic(PSConvertBinder.Get(lhsType), lhsType,
|
|
GetTransformedExpression(argumentTransformationAttributes, value.Expression));
|
|
}
|
|
else
|
|
{
|
|
assignedValue = (lhsType == typeof(object) && value.LimitType == typeof(PSObject))
|
|
? Expression.Call(CachedReflectionInfo.PSObject_Base, value.Expression.Cast(typeof(PSObject)))
|
|
: value.CastOrConvert(lhsType);
|
|
}
|
|
|
|
expr = Expression.Block(
|
|
new[] { tmp },
|
|
Expression.Assign(tmp, assignedValue),
|
|
Expression.Assign(lhs, tmp),
|
|
tmp.Cast(typeof(object)));
|
|
}
|
|
|
|
var e = Expression.Variable(typeof(Exception));
|
|
expr = Expression.TryCatch(expr.Cast(typeof(object)),
|
|
Expression.Catch(e,
|
|
Expression.Block(
|
|
Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToMethodInvocationException,
|
|
e,
|
|
Expression.Constant(typeof(SetValueInvocationException), typeof(Type)),
|
|
Expression.Constant(Name),
|
|
ExpressionCache.Constant(0),
|
|
Expression.Constant(null, typeof(MemberInfo))),
|
|
Expression.Rethrow(typeof(object)))));
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
var codeProperty = psPropertyInfo as PSCodeProperty;
|
|
if (codeProperty != null)
|
|
{
|
|
var setterMethod = codeProperty.SetterCodeReference;
|
|
var parameters = setterMethod.GetParameters();
|
|
var propertyType = parameters[parameters.Length - 1].ParameterType;
|
|
|
|
if (propertyType.IsByRefLike)
|
|
{
|
|
var expr = Expression.Throw(
|
|
Expression.New(
|
|
CachedReflectionInfo.SetValueException_ctor,
|
|
Expression.Constant(nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField)),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField),
|
|
Expression.NewArrayInit(
|
|
typeof(object),
|
|
Expression.Constant(codeProperty.Name),
|
|
Expression.Constant(propertyType, typeof(Type)))),
|
|
this.ReturnType);
|
|
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var temp = Expression.Variable(typeof(object));
|
|
return new DynamicMetaObject(
|
|
Expression.Block(
|
|
new[] { temp },
|
|
Expression.Assign(temp, value.CastOrConvert(temp.Type)),
|
|
PSInvokeMemberBinder.InvokeMethod(
|
|
setterMethod,
|
|
target: null,
|
|
new[] { target, value },
|
|
expandParameters: false,
|
|
PSInvokeMemberBinder.MethodInvocationType.Setter),
|
|
temp),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var scriptProperty = psPropertyInfo as PSScriptProperty;
|
|
if (scriptProperty != null)
|
|
{
|
|
// Invoke Setter
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(Expression.Constant(scriptProperty, typeof(PSScriptProperty)),
|
|
CachedReflectionInfo.PSScriptProperty_InvokeSetter,
|
|
PSGetMemberBinder.GetTargetExpr(target), value.Expression.Cast(typeof(object))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
Diagnostics.Assert(false, "The property we're trying to set was unexpected.");
|
|
}
|
|
|
|
if (errorSuggestion != null)
|
|
{
|
|
return errorSuggestion.WriteToDebugLog(this);
|
|
}
|
|
|
|
// If we get here, the property isn't settable. Call SetAdaptedValue, which will eventually call the setter and raise an exception
|
|
// with a suitable error message.
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.PSSetMemberBinder_SetAdaptedValue,
|
|
PSGetMemberBinder.GetTargetExpr(target, typeof(object)), Expression.Constant(Name),
|
|
value.Expression.Cast(typeof(object))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
private DynamicMetaObject GeneratePropertyAssignmentException(BindingRestrictions restrictions)
|
|
{
|
|
Expression innerException = Expression.New(CachedReflectionInfo.SetValueException_ctor,
|
|
Expression.Constant("PropertyAssignmentException"),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ParserStrings.PropertyIsReadOnly),
|
|
Expression.NewArrayInit(typeof(object), Expression.Constant(Name)));
|
|
var expr = Compiler.ThrowRuntimeErrorWithInnerException("PropertyAssignmentException",
|
|
Expression.Constant(ParserStrings.PropertyIsReadOnly), innerException,
|
|
this.ReturnType, Expression.Constant(Name));
|
|
return new DynamicMetaObject(expr, restrictions);
|
|
}
|
|
|
|
internal static object SetAdaptedValue(object obj, string member, object value)
|
|
{
|
|
try
|
|
{
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
PSMemberInfo memberInfo = null;
|
|
|
|
if ((context != null) && (context.TypeTable != null))
|
|
{
|
|
ConsolidatedString typenames = PSObject.GetTypeNames(obj);
|
|
memberInfo = context.TypeTable.GetMembers<PSMemberInfo>(typenames)[member];
|
|
if (memberInfo != null)
|
|
{
|
|
memberInfo = PSGetMemberBinder.CloneMemberInfo(memberInfo, obj);
|
|
}
|
|
}
|
|
|
|
var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable);
|
|
if (memberInfo == null)
|
|
{
|
|
memberInfo = adapterSet.OriginalAdapter.BaseGetMember<PSMemberInfo>(obj, member);
|
|
}
|
|
|
|
if (memberInfo == null && adapterSet.DotNetAdapter != null)
|
|
{
|
|
memberInfo = adapterSet.DotNetAdapter.BaseGetMember<PSMemberInfo>(obj, member);
|
|
}
|
|
|
|
if (memberInfo != null)
|
|
{
|
|
memberInfo.Value = value;
|
|
}
|
|
else
|
|
{
|
|
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),
|
|
null, "PropertyAssignmentException", ParserStrings.PropertyNotFound, member);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
catch (SetValueException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ExceptionHandlingOps.ConvertToMethodInvocationException(e, typeof(SetValueInvocationException), member, 0);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate regular binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSSetMemberBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._getMemberBinder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class PSInvokeBinder : InvokeBinder
|
|
{
|
|
internal PSInvokeBinder(CallInfo callInfo) : base(callInfo)
|
|
{
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return errorSuggestion ?? target.ThrowRuntimeError(args, BindingRestrictions.Empty, "CannotInvoke", ParserStrings.CannotInvoke);
|
|
}
|
|
}
|
|
|
|
internal sealed class PSInvokeMemberBinder : InvokeMemberBinder
|
|
{
|
|
internal enum MethodInvocationType
|
|
{
|
|
Ordinary,
|
|
Setter,
|
|
Getter,
|
|
BaseCtor,
|
|
NonVirtual,
|
|
}
|
|
|
|
private sealed class KeyComparer : IEqualityComparer<PSInvokeMemberBinderKeyType>
|
|
{
|
|
public bool Equals(PSInvokeMemberBinderKeyType x, PSInvokeMemberBinderKeyType y)
|
|
{
|
|
return x.Item1.Equals(y.Item1, StringComparison.OrdinalIgnoreCase)
|
|
&& x.Item2.Equals(y.Item2)
|
|
&& x.Item3 == y.Item3
|
|
&& x.Item4 == y.Item4
|
|
&& ((x.Item5 == null) ? (y.Item5 == null) : x.Item5.Equals(y.Item5))
|
|
&& x.Item6 == y.Item6
|
|
&& x.Item7 == y.Item7;
|
|
}
|
|
|
|
public int GetHashCode(PSInvokeMemberBinderKeyType obj)
|
|
{
|
|
return Utils.CombineHashCodes(
|
|
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Item1),
|
|
obj.Item2.GetHashCode(),
|
|
obj.Item3.GetHashCode(),
|
|
obj.Item4.GetHashCode(),
|
|
obj.Item5 == null ? 0 : obj.Item5.GetHashCode(),
|
|
obj.Item6.GetHashCode(),
|
|
obj.Item7 == null ? 0 : obj.Item7.GetHashCode());
|
|
}
|
|
}
|
|
|
|
private static readonly
|
|
Dictionary<PSInvokeMemberBinderKeyType, PSInvokeMemberBinder> s_binderCache = new Dictionary<PSInvokeMemberBinderKeyType, PSInvokeMemberBinder>(new KeyComparer());
|
|
|
|
internal readonly PSMethodInvocationConstraints _invocationConstraints;
|
|
internal readonly PSGetMemberBinder _getMemberBinder;
|
|
|
|
private PSInvokeMemberBinder GetNonEnumeratingBinder()
|
|
{
|
|
return Get(Name, _classScope, CallInfo, @static: false, propertySetter: _propertySetter, nonEnumerating: true, constraints: _invocationConstraints);
|
|
}
|
|
|
|
public static PSInvokeMemberBinder Get(string memberName, CallInfo callInfo, bool @static, bool propertySetter,
|
|
PSMethodInvocationConstraints constraints, Type classScope)
|
|
{
|
|
return Get(memberName, classScope, callInfo, @static, propertySetter, nonEnumerating: false, constraints: constraints);
|
|
}
|
|
|
|
private static PSInvokeMemberBinder Get(string memberName, Type classScope, CallInfo callInfo, bool @static, bool propertySetter,
|
|
bool nonEnumerating, PSMethodInvocationConstraints constraints)
|
|
{
|
|
PSInvokeMemberBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
var key = Tuple.Create(memberName, callInfo, propertySetter, nonEnumerating, constraints, @static, classScope);
|
|
if (!s_binderCache.TryGetValue(key, out result))
|
|
{
|
|
result = new PSInvokeMemberBinder(memberName, true, @static, propertySetter, nonEnumerating, callInfo, constraints, classScope);
|
|
s_binderCache.Add(key, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private readonly bool _static;
|
|
private readonly bool _propertySetter;
|
|
private readonly bool _nonEnumerating;
|
|
private readonly Type _classScope;
|
|
|
|
private PSInvokeMemberBinder(string name,
|
|
bool ignoreCase,
|
|
bool @static,
|
|
bool propertySetter,
|
|
bool nonEnumerating,
|
|
CallInfo callInfo,
|
|
PSMethodInvocationConstraints invocationConstraints,
|
|
Type classScope)
|
|
: base(name, ignoreCase, callInfo)
|
|
{
|
|
_static = @static;
|
|
_propertySetter = propertySetter;
|
|
_nonEnumerating = nonEnumerating;
|
|
this._invocationConstraints = invocationConstraints;
|
|
_classScope = classScope;
|
|
this._getMemberBinder = PSGetMemberBinder.Get(name, classScope, @static);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture,
|
|
"PSInvokeMember: {0}{1}{2} ver:{3} args:{4} constraints:<{5}>", _static ? "static " : string.Empty, _propertySetter ? "propset " : string.Empty,
|
|
Name, _getMemberBinder._version, CallInfo.ArgumentCount, _invocationConstraints != null ? _invocationConstraints.ToString() : string.Empty);
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || args.Any(static arg => !arg.HasValue))
|
|
{
|
|
return Defer(args.Prepend(target).ToArray());
|
|
}
|
|
|
|
// Defer COM objects or arguments wrapped in PSObjects
|
|
if ((target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) ||
|
|
args.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value)))
|
|
{
|
|
object baseObject = PSObject.Base(target.Value);
|
|
if (baseObject != null && Marshal.IsComObject(baseObject))
|
|
{
|
|
// We unwrap only if the 'base' of 'target' is a COM object. It's unnecessary to unwrap in other cases,
|
|
// especially in the case that 'target' is a string, we would lose instance members on the PSObject.
|
|
// Therefore, we need to use a stricter restriction to make sure other type of PSObject 'target'
|
|
// doesn't get unwrapped.
|
|
return this.DeferForPSObject(args.Prepend(target).ToArray(), targetIsComObject: true).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
// Check if this is a COM Object
|
|
DynamicMetaObject result;
|
|
if (ComInterop.ComBinder.TryBindInvokeMember(this, _propertySetter, target, args, out result))
|
|
{
|
|
return result.UpdateComRestrictionsForPsObject(args).WriteToDebugLog(this);
|
|
}
|
|
|
|
var targetValue = PSObject.Base(target.Value);
|
|
if (targetValue == null)
|
|
{
|
|
if (!_static && !_nonEnumerating)
|
|
{
|
|
// As discussed with Bruce, the Where/ForEach operators should work on $null and return an empty collection.
|
|
// e.g. $null.Where{"I didn't run"} should return an empty collection
|
|
DynamicMetaObject emptyEnumerator = new DynamicMetaObject(
|
|
Expression.Call(Expression.NewArrayInit(typeof(object)), CachedReflectionInfo.IEnumerable_GetEnumerator),
|
|
BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression), null))
|
|
.WriteToDebugLog(this);
|
|
BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
|
|
|
|
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "InvokeMethodOnNull", ParserStrings.InvokeMethodOnNull).WriteToDebugLog(this);
|
|
}
|
|
|
|
PSMemberInfo memberInfo;
|
|
if (_getMemberBinder.HasInstanceMember && PSGetMemberBinder.TryGetInstanceMember(target.Value, Name, out memberInfo))
|
|
{
|
|
// If there is an instance member, we generate (roughly) the following:
|
|
// PSMethodInfo methodInfo;
|
|
// if (PSInvokeMemberBinder.TryGetInstanceMethod(target.Value, Name, out methodInfoInfo))
|
|
// return methodInfo.Invoke(target, args);
|
|
// else
|
|
// update the site
|
|
// We use a generic method like this because:
|
|
// * If one object has an instance property with a given name, it's like many others do as well
|
|
// * We want to avoid generating new sites for every object with an instance member
|
|
// As an alternative, would could generate the following psuedo-code:
|
|
// if (target.Value == previousInstance)
|
|
// return optimized value (depending on the exact PSMemberInfo subclass)
|
|
// else update the site
|
|
// But the assumption here is that many sites probably performs worse than the dictionary lookup
|
|
// and unoptimized virtual call to PSMemberInfo.Value.
|
|
//
|
|
// The binding restrictions could avoid a version check because it's never wrong to look for an instance member,
|
|
// but we add the check because the DLR requires a non-empty check when the target implements IDynamicMetaObjectProvider,
|
|
// which PSObject does. The version check is also marginally useful if we knew we'd never see another
|
|
// instance member with this member name, but we're not tracking things to make that a useful test.
|
|
|
|
var methodInfoVar = Expression.Variable(typeof(PSMethodInfo));
|
|
var expr = Expression.Condition(
|
|
Expression.Call(CachedReflectionInfo.PSInvokeMemberBinder_TryGetInstanceMethod,
|
|
target.Expression.Cast(typeof(object)), Expression.Constant(Name), methodInfoVar),
|
|
Expression.Call(methodInfoVar, CachedReflectionInfo.PSMethodInfo_Invoke,
|
|
Expression.NewArrayInit(typeof(object), args.Select(static dmo => dmo.Expression.Cast(typeof(object))))),
|
|
this.GetUpdateExpression(typeof(object)));
|
|
|
|
return (new DynamicMetaObject(Expression.Block(new[] { methodInfoVar }, expr),
|
|
BinderUtils.GetVersionCheck(_getMemberBinder, _getMemberBinder._version))).WriteToDebugLog(this);
|
|
}
|
|
|
|
BindingRestrictions restrictions;
|
|
bool canOptimize;
|
|
Type aliasConversionType;
|
|
var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Method) as PSMethodInfo;
|
|
restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
|
|
|
|
// If the process has ever used ConstrainedLanguage, then we need to add the language mode
|
|
// to the binding restrictions, and check whether it is allowed. We can't limit
|
|
// the language check to unsafe types, as a safe type might have an unsafe method.
|
|
if (ExecutionContext.HasEverUsedConstrainedLanguage)
|
|
{
|
|
restrictions = restrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());
|
|
|
|
// Validate that this is allowed in the current language mode
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode(
|
|
context, target, targetValue, Name, _static, args, restrictions,
|
|
"MethodInvocationNotSupportedInConstrainedLanguage", ParserStrings.InvokeMethodConstrainedLanguage);
|
|
if (runtimeError != null)
|
|
{
|
|
return runtimeError.WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
if (!canOptimize)
|
|
{
|
|
Diagnostics.Assert(methodInfo == null, "We don't bother returning members if we can't optimize.");
|
|
|
|
Expression call;
|
|
if (_propertySetter)
|
|
{
|
|
call = Expression.Call(
|
|
CachedReflectionInfo.PSInvokeMemberBinder_InvokeAdaptedSetMember,
|
|
PSGetMemberBinder.GetTargetExpr(target, typeof(object)),
|
|
Expression.Constant(Name),
|
|
Expression.NewArrayInit(typeof(object),
|
|
args.Take(args.Length - 1).Select(static arg => arg.Expression.Cast(typeof(object)))),
|
|
args.Last().Expression.Cast(typeof(object)));
|
|
}
|
|
else
|
|
{
|
|
call = Expression.Call(
|
|
CachedReflectionInfo.PSInvokeMemberBinder_InvokeAdaptedMember,
|
|
PSGetMemberBinder.GetTargetExpr(target, typeof(object)),
|
|
Expression.Constant(Name),
|
|
Expression.NewArrayInit(typeof(object),
|
|
args.Select(static arg => arg.Expression.Cast(typeof(object)))));
|
|
}
|
|
|
|
return new DynamicMetaObject(call, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
// If the target value is a PSObject and its base object happens to be a Hashtable or ArrayList,
|
|
// we might have three interesting cases here:
|
|
// (1) the target value could be a regular PSObject that wraps the Hashtable/ArrayList, i.e. $target = [PSObject]::AsPSObject($hash)
|
|
// (2) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be false, i.e. deserialized Hashtable/ArrayList/Dictionary[string, string]
|
|
// (3) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be true, i.e. deserialized XmlElement
|
|
// For the first two cases, it's OK to call a .NET method from the base object, such as $target.Add().
|
|
// For the third case, calling a .NET method from the base object is incorrect, because the original type of the deserialized object doesn't have the method.
|
|
// example: XmlElement derives from IEnumerable, so it's treated as a container object when powershell does the serialization -- using an ArrayList to hold
|
|
// its elements -- but we cannot call Add() on it.
|
|
//
|
|
// We add restriction to do this check only if the methodInfo is a .NET method/parameterizedProperty, otherwise it's not affected by the above cases, for example, a PSScriptMethod
|
|
// defined in the TypeTable will only get affected by the PSTypeNames.
|
|
if (methodInfo is PSMethod || methodInfo is PSParameterizedProperty)
|
|
{
|
|
var psObj = target.Value as PSObject;
|
|
if (psObj != null && (targetValue.GetType() == typeof(Hashtable) || targetValue.GetType() == typeof(ArrayList)))
|
|
{
|
|
// If we get here, then the target value should have 'isDeserialized == false', otherwise we cannot get a .NET methodInfo
|
|
// from _getMemberBinder.GetPSMemberInfo(). This is because when 'isDeserialized' is true, we use the PSObject to find the
|
|
// corresponding Adapter -- PSObjectAdapter, which cannot be optimized.
|
|
Diagnostics.Assert(!psObj.IsDeserialized,
|
|
"isDeserialized should be false, because if not, we cannot get a .NET method/parameterizedProperty from GetPSMemberInfo");
|
|
|
|
restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Not(Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized))));
|
|
}
|
|
}
|
|
|
|
var psMethod = methodInfo as PSMethod;
|
|
if (psMethod != null)
|
|
{
|
|
var data = (DotNetAdapter.MethodCacheEntry)psMethod.adapterData;
|
|
|
|
return InvokeDotNetMethod(CallInfo, Name, _invocationConstraints, _propertySetter ? MethodInvocationType.Setter : MethodInvocationType.Ordinary, target, args, restrictions,
|
|
data.methodInformationStructures, typeof(MethodException)).WriteToDebugLog(this);
|
|
}
|
|
|
|
var scriptMethod = methodInfo as PSScriptMethod;
|
|
if (scriptMethod != null)
|
|
{
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.PSScriptMethod_InvokeScript,
|
|
Expression.Constant(Name),
|
|
Expression.Constant(scriptMethod.Script),
|
|
target.Expression.Cast(typeof(object)),
|
|
Expression.NewArrayInit(typeof(object),
|
|
args.Select(static e => e.Expression.Cast(typeof(object))))),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var codeMethod = methodInfo as PSCodeMethod;
|
|
if (codeMethod != null)
|
|
{
|
|
Expression expr = InvokeMethod(codeMethod.CodeReference, null, args.Prepend(target).ToArray(), false, MethodInvocationType.Ordinary);
|
|
if (codeMethod.CodeReference.ReturnType == typeof(void))
|
|
{
|
|
expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant);
|
|
}
|
|
else
|
|
{
|
|
expr = expr.Cast(typeof(object));
|
|
}
|
|
|
|
return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var parameterizedProperty = methodInfo as PSParameterizedProperty;
|
|
if (parameterizedProperty != null)
|
|
{
|
|
var p = (DotNetAdapter.ParameterizedPropertyCacheEntry)parameterizedProperty.adapterData;
|
|
return InvokeDotNetMethod(CallInfo, Name, _invocationConstraints, _propertySetter ? MethodInvocationType.Setter : MethodInvocationType.Ordinary, target, args, restrictions,
|
|
_propertySetter ? p.setterInformation : p.getterInformation,
|
|
_propertySetter ? typeof(SetValueInvocationException) : typeof(GetValueInvocationException)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (errorSuggestion != null)
|
|
{
|
|
return errorSuggestion.WriteToDebugLog(this);
|
|
}
|
|
|
|
// See comment on PSGetMemberBinder.PropertyDoesntExistCheckSpecialCases - the same applies here for method calls.
|
|
if (!_static && !_nonEnumerating && target.Value != AutomationNull.Value)
|
|
{
|
|
// Invoking Where and ForEach operators on collections.
|
|
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return InvokeWhereOnCollection(target, args, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return InvokeForEachOnCollection(target, args, restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var enumerableTarget = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerableTarget != null)
|
|
{
|
|
// Try calling the method on each member of the collection.
|
|
return InvokeMemberOnCollection(enumerableTarget, args, targetValue.GetType(), restrictions).WriteToDebugLog(this);
|
|
}
|
|
}
|
|
|
|
var typeForMessage = _static && targetValue is Type ? (Type)targetValue : targetValue.GetType();
|
|
return new DynamicMetaObject(
|
|
Compiler.ThrowRuntimeError(ParserOps.MethodNotFoundErrorId, ParserStrings.MethodNotFound,
|
|
Expression.Constant(typeForMessage.FullName), Expression.Constant(Name)),
|
|
restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
internal static DynamicMetaObject InvokeDotNetMethod(
|
|
CallInfo callInfo,
|
|
string name,
|
|
PSMethodInvocationConstraints psMethodInvocationConstraints,
|
|
MethodInvocationType methodInvocationType,
|
|
DynamicMetaObject target,
|
|
DynamicMetaObject[] args,
|
|
BindingRestrictions restrictions,
|
|
MethodInformation[] mi,
|
|
Type errorExceptionType)
|
|
{
|
|
bool expandParamsOnBest;
|
|
bool callNonVirtually;
|
|
string errorId = null;
|
|
string errorMsg = null;
|
|
int numArgs = args.Length;
|
|
if (methodInvocationType == MethodInvocationType.Setter)
|
|
{
|
|
numArgs -= 1;
|
|
}
|
|
|
|
object[] argValues = new object[numArgs];
|
|
for (int i = 0; i < numArgs; ++i)
|
|
{
|
|
object arg = args[i].Value;
|
|
argValues[i] = arg == AutomationNull.Value ? null : arg;
|
|
}
|
|
|
|
var result = Adapter.FindBestMethod(
|
|
mi,
|
|
psMethodInvocationConstraints,
|
|
allowCastingToByRefLikeType: true,
|
|
argValues,
|
|
ref errorId,
|
|
ref errorMsg,
|
|
out expandParamsOnBest,
|
|
out callNonVirtually);
|
|
|
|
if (callNonVirtually && methodInvocationType != MethodInvocationType.BaseCtor)
|
|
{
|
|
methodInvocationType = MethodInvocationType.NonVirtual;
|
|
}
|
|
|
|
if (result != null)
|
|
{
|
|
var methodInfo = result.method;
|
|
var expr = InvokeMethod(methodInfo, target, args, expandParamsOnBest, methodInvocationType);
|
|
if (expr.Type == typeof(void))
|
|
{
|
|
expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant);
|
|
}
|
|
|
|
// If we're calling SteppablePipeline.{Begin|Process|End}, we don't want
|
|
// to wrap exceptions - this is very much a special case to help error
|
|
// propagation and ensure errors are attributed to the correct code (the
|
|
// cmdlet invoked, not the method call from some proxy.)
|
|
if (methodInfo.DeclaringType == typeof(SteppablePipeline)
|
|
&& (methodInfo.Name.Equals("Begin", StringComparison.Ordinal))
|
|
|| methodInfo.Name.Equals("Process", StringComparison.Ordinal)
|
|
|| methodInfo.Name.Equals("End", StringComparison.Ordinal))
|
|
{
|
|
return new DynamicMetaObject(expr, restrictions);
|
|
}
|
|
|
|
expr = expr.Cast(typeof(object));
|
|
|
|
// Likewise, when calling methods in types defined by PowerShell, we don't
|
|
// want to wrap the exception.
|
|
if (methodInfo.DeclaringType.Assembly.GetCustomAttributes(typeof(DynamicClassImplementationAssemblyAttribute)).Any())
|
|
{
|
|
return new DynamicMetaObject(expr, restrictions);
|
|
}
|
|
|
|
var e = Expression.Variable(typeof(Exception));
|
|
expr = Expression.TryCatch(expr,
|
|
Expression.Catch(e,
|
|
Expression.Block(
|
|
expr.Type,
|
|
Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToMethodInvocationException,
|
|
e,
|
|
Expression.Constant(errorExceptionType, typeof(Type)),
|
|
Expression.Constant(methodInfo.Name),
|
|
ExpressionCache.Constant(args.Length),
|
|
Expression.Constant(methodInfo, typeof(MethodBase))),
|
|
Expression.Rethrow(expr.Type))));
|
|
|
|
return new DynamicMetaObject(expr, restrictions);
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Compiler.CreateThrow(typeof(object), errorExceptionType,
|
|
new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
|
|
errorId, null, errorMsg, new object[] { name, callInfo.ArgumentCount }),
|
|
restrictions);
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target,
|
|
DynamicMetaObject[] args,
|
|
DynamicMetaObject errorSuggestion)
|
|
{
|
|
return (errorSuggestion ?? new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(
|
|
new PSInvokeBinder(CallInfo),
|
|
typeof(object),
|
|
args.Prepend(target).Select(static dmo => dmo.Expression)
|
|
),
|
|
target.Restrictions.Merge(BindingRestrictions.Combine(args))
|
|
));
|
|
}
|
|
|
|
internal static MethodInfo FindBestMethod(DynamicMetaObject target,
|
|
IEnumerable<DynamicMetaObject> args,
|
|
string methodName,
|
|
bool @static,
|
|
PSMethodInvocationConstraints invocationConstraints)
|
|
{
|
|
MethodInfo result = null;
|
|
|
|
var psMethod = PSObject.DotNetInstanceAdapter.GetDotNetMethod<PSMethod>(PSObject.Base(target.Value), methodName);
|
|
if (psMethod != null)
|
|
{
|
|
var data = (DotNetAdapter.MethodCacheEntry)psMethod.adapterData;
|
|
|
|
string errorId = null;
|
|
string errorMsg = null;
|
|
bool expandParameters;
|
|
bool callNonVirtually;
|
|
|
|
var mi = Adapter.FindBestMethod(
|
|
data.methodInformationStructures,
|
|
invocationConstraints,
|
|
allowCastingToByRefLikeType: true,
|
|
args.Select(static arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(),
|
|
ref errorId,
|
|
ref errorMsg,
|
|
out expandParameters,
|
|
out callNonVirtually);
|
|
|
|
if (mi != null)
|
|
{
|
|
result = (MethodInfo)mi.method;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, DynamicMetaObject[] args, bool expandParameters, MethodInvocationType invocationType)
|
|
{
|
|
List<ParameterExpression> temps = new List<ParameterExpression>();
|
|
List<Expression> initTemps = new List<Expression>();
|
|
List<Expression> copyOutTemps = new List<Expression>();
|
|
|
|
ConstructorInfo constructorInfo = null;
|
|
MethodInfo methodInfo = mi as MethodInfo;
|
|
if (methodInfo != null)
|
|
{
|
|
Type returnType = methodInfo.ReturnType;
|
|
if (returnType.IsByRefLike)
|
|
{
|
|
ConstructorInfo exceptionCtorInfo;
|
|
switch (invocationType)
|
|
{
|
|
case MethodInvocationType.Getter:
|
|
exceptionCtorInfo = CachedReflectionInfo.GetValueException_ctor;
|
|
break;
|
|
case MethodInvocationType.Setter:
|
|
exceptionCtorInfo = CachedReflectionInfo.SetValueException_ctor;
|
|
break;
|
|
default:
|
|
exceptionCtorInfo = CachedReflectionInfo.MethodException_ctor;
|
|
break;
|
|
}
|
|
|
|
return Expression.Throw(
|
|
Expression.New(
|
|
exceptionCtorInfo,
|
|
Expression.Constant(nameof(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType)),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType),
|
|
Expression.NewArrayInit(
|
|
typeof(object),
|
|
Expression.Constant(methodInfo.Name),
|
|
Expression.Constant(returnType, typeof(Type)))),
|
|
typeof(object));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
constructorInfo = (ConstructorInfo)mi;
|
|
Type declaringType = constructorInfo.DeclaringType;
|
|
if (declaringType.IsByRefLike)
|
|
{
|
|
return Expression.Throw(
|
|
Expression.New(
|
|
CachedReflectionInfo.MethodException_ctor,
|
|
Expression.Constant(nameof(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType)),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType),
|
|
Expression.NewArrayInit(
|
|
typeof(object),
|
|
Expression.Constant(declaringType, typeof(Type)))),
|
|
typeof(object));
|
|
}
|
|
}
|
|
|
|
// Invoking a base constructor or a base method (non-virtual call) depends reflection invocation
|
|
// via helper methods, and thus all arguments need to be casted to 'object'. The ByRef-like types
|
|
// cannot be boxed and won't work with reflection.
|
|
bool allowCastingToByRefLikeType =
|
|
invocationType != MethodInvocationType.BaseCtor &&
|
|
invocationType != MethodInvocationType.NonVirtual;
|
|
var parameters = mi.GetParameters();
|
|
var argExprs = new Expression[parameters.Length];
|
|
|
|
for (int i = 0; i < parameters.Length; ++i)
|
|
{
|
|
Type parameterType = parameters[i].ParameterType;
|
|
string paramName = parameters[i].Name;
|
|
if (string.IsNullOrWhiteSpace(paramName))
|
|
{
|
|
paramName = i.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
// The extension method 'CustomAttributeExtensions.GetCustomAttributes(ParameterInfo, Type, Boolean)' has inconsistent
|
|
// behavior on its return value in both FullCLR and CoreCLR. According to MSDN, if the attribute cannot be found, it
|
|
// should return an empty collection. However, it returns null in some rare cases [when the parameter isn't backed by
|
|
// actual metadata].
|
|
// This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling
|
|
// against CoreCLR. So we need to add a null check until this is fixed in CLR.
|
|
var paramArrayAttrs = parameters[i].GetCustomAttributes(typeof(ParamArrayAttribute), false);
|
|
if (paramArrayAttrs != null && paramArrayAttrs.Length > 0)
|
|
{
|
|
Diagnostics.Assert(i == parameters.Length - 1, "vararg parameter is not the last");
|
|
var paramElementType = parameterType.GetElementType();
|
|
|
|
if (expandParameters)
|
|
{
|
|
argExprs[i] = Expression.NewArrayInit(
|
|
paramElementType,
|
|
args.Skip(i).Select(
|
|
a => a.CastOrConvertMethodArgument(
|
|
paramElementType,
|
|
paramName,
|
|
mi.Name,
|
|
allowCastingToByRefLikeType: false,
|
|
temps,
|
|
initTemps)));
|
|
}
|
|
else
|
|
{
|
|
var arg = args[i].CastOrConvertMethodArgument(
|
|
parameterType,
|
|
paramName,
|
|
mi.Name,
|
|
allowCastingToByRefLikeType: false,
|
|
temps,
|
|
initTemps);
|
|
argExprs[i] = arg;
|
|
}
|
|
}
|
|
else if (i >= args.Length)
|
|
{
|
|
Diagnostics.Assert(parameters[i].IsOptional,
|
|
"if there are too few arguments, FindBestMethod should only succeed if parameters are optional");
|
|
var argValue = parameters[i].DefaultValue;
|
|
if (argValue == null)
|
|
{
|
|
argExprs[i] = Expression.Default(parameterType);
|
|
}
|
|
else
|
|
{
|
|
// We don't specify the parameter type in the constant expression. Normally the default
|
|
// argument's type should match the parameter type, but sometimes it won't, e.g. with COM,
|
|
// the default can be System.Reflection.Missing.Value and this value is handled specially.
|
|
argExprs[i] = Expression.Constant(argValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (parameterType.IsByRef)
|
|
{
|
|
if (args[i].Value is not PSReference)
|
|
{
|
|
return Compiler.CreateThrow(typeof(object), typeof(MethodException),
|
|
new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) },
|
|
"NonRefArgumentToRefParameterMsg", null, ExtendedTypeSystem.NonRefArgumentToRefParameter,
|
|
new object[] { i + 1, typeof(PSReference).FullName, "[ref]" });
|
|
}
|
|
|
|
var temp = Expression.Variable(parameterType.GetElementType());
|
|
temps.Add(temp);
|
|
var psRefValue = Expression.Property(args[i].Expression.Cast(typeof(PSReference)), CachedReflectionInfo.PSReference_Value);
|
|
initTemps.Add(Expression.Assign(temp, psRefValue.Convert(temp.Type)));
|
|
copyOutTemps.Add(Expression.Assign(psRefValue, temp.Cast(typeof(object))));
|
|
argExprs[i] = temp;
|
|
}
|
|
else
|
|
{
|
|
argExprs[i] = args[i].CastOrConvertMethodArgument(
|
|
parameterType,
|
|
paramName,
|
|
mi.Name,
|
|
allowCastingToByRefLikeType,
|
|
temps,
|
|
initTemps);
|
|
}
|
|
}
|
|
}
|
|
|
|
Expression call;
|
|
if (constructorInfo != null)
|
|
{
|
|
if (invocationType == MethodInvocationType.BaseCtor)
|
|
{
|
|
var targetExpr = target.Value is PSObject
|
|
? target.Expression.Cast(constructorInfo.DeclaringType)
|
|
: PSGetMemberBinder.GetTargetExpr(target, constructorInfo.DeclaringType);
|
|
call = Expression.Call(
|
|
CachedReflectionInfo.ClassOps_CallBaseCtor,
|
|
targetExpr,
|
|
Expression.Constant(constructorInfo, typeof(ConstructorInfo)),
|
|
Expression.NewArrayInit(typeof(object), argExprs.Select(static x => x.Cast(typeof(object)))));
|
|
}
|
|
else
|
|
{
|
|
call = Expression.New(constructorInfo, argExprs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (invocationType == MethodInvocationType.NonVirtual && !methodInfo.IsStatic)
|
|
{
|
|
call = Expression.Call(
|
|
methodInfo.ReturnType == typeof(void)
|
|
? CachedReflectionInfo.ClassOps_CallVoidMethodNonVirtually
|
|
: CachedReflectionInfo.ClassOps_CallMethodNonVirtually,
|
|
PSGetMemberBinder.GetTargetExpr(target, methodInfo.DeclaringType),
|
|
Expression.Constant(methodInfo, typeof(MethodInfo)),
|
|
Expression.NewArrayInit(typeof(object), argExprs.Select(static x => x.Cast(typeof(object)))));
|
|
}
|
|
else
|
|
{
|
|
call = methodInfo.IsStatic
|
|
? Expression.Call(methodInfo, argExprs)
|
|
: Expression.Call(
|
|
PSGetMemberBinder.GetTargetExpr(target, methodInfo.DeclaringType),
|
|
methodInfo, argExprs);
|
|
}
|
|
}
|
|
|
|
if (temps.Count > 0)
|
|
{
|
|
if (call.Type != typeof(void) && copyOutTemps.Count > 0)
|
|
{
|
|
var retValue = Expression.Variable(call.Type);
|
|
temps.Add(retValue);
|
|
call = Expression.Assign(retValue, call);
|
|
copyOutTemps.Add(retValue);
|
|
}
|
|
|
|
call = Expression.Block(call.Type, temps, initTemps.Append(call).Concat(copyOutTemps));
|
|
}
|
|
|
|
return call;
|
|
}
|
|
|
|
private DynamicMetaObject InvokeMemberOnCollection(DynamicMetaObject targetEnumerator, DynamicMetaObject[] args, Type typeForMessage, BindingRestrictions restrictions)
|
|
{
|
|
var d = DynamicExpression.Dynamic(this, this.ReturnType, args.Select(static a => a.Expression).Prepend(ExpressionCache.NullConstant));
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_MethodInvoker,
|
|
Expression.Constant(this.GetNonEnumeratingBinder()),
|
|
Expression.Constant(d.DelegateType, typeof(Type)),
|
|
targetEnumerator.Expression,
|
|
Expression.NewArrayInit(typeof(object),
|
|
args.Select(static a => a.Expression.Cast(typeof(object)))),
|
|
Expression.Constant(typeForMessage, typeof(Type))
|
|
),
|
|
targetEnumerator.Restrictions.Merge(restrictions));
|
|
}
|
|
|
|
private static DynamicMetaObject GetTargetAsEnumerable(DynamicMetaObject target)
|
|
{
|
|
var enumerableTarget = PSEnumerableBinder.IsEnumerable(target);
|
|
if (enumerableTarget == null)
|
|
{
|
|
// Wrap the target in an array.
|
|
enumerableTarget = PSEnumerableBinder.IsEnumerable(
|
|
new DynamicMetaObject(
|
|
Expression.NewArrayInit(typeof(object), target.Expression.Cast(typeof(object))),
|
|
target.GetSimpleTypeRestriction()));
|
|
}
|
|
|
|
return enumerableTarget;
|
|
}
|
|
|
|
/// <param name="target">The target to operate against.</param>
|
|
/// <param name="args">
|
|
/// Arguments to the operator. The first argument must be either a scriptblock
|
|
/// or a string representing a 'simple where' expression. The second is an enum that controls
|
|
/// the matching behaviour returning the first, last or all matching elements.
|
|
/// </param>
|
|
/// <param name="argRestrictions">The binding restrictions for the arguments.</param>
|
|
private DynamicMetaObject InvokeWhereOnCollection(DynamicMetaObject target, DynamicMetaObject[] args, BindingRestrictions argRestrictions)
|
|
{
|
|
var lhsEnumerator = GetTargetAsEnumerable(target);
|
|
|
|
switch (args.Length)
|
|
{
|
|
case 1:
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_Where,
|
|
lhsEnumerator.Expression,
|
|
PSGetMemberBinder.GetTargetExpr(args[0]).Convert(typeof(ScriptBlock)),
|
|
Expression.Constant(WhereOperatorSelectionMode.Default),
|
|
Expression.Constant(0)),
|
|
lhsEnumerator.Restrictions.Merge(argRestrictions));
|
|
case 2:
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_Where,
|
|
lhsEnumerator.Expression,
|
|
PSGetMemberBinder.GetTargetExpr(args[0]).Convert(typeof(ScriptBlock)),
|
|
PSGetMemberBinder.GetTargetExpr(args[1]).Convert(typeof(WhereOperatorSelectionMode)),
|
|
Expression.Constant(0)),
|
|
lhsEnumerator.Restrictions.Merge(argRestrictions));
|
|
case 3:
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_Where,
|
|
lhsEnumerator.Expression,
|
|
PSGetMemberBinder.GetTargetExpr(args[0]).Convert(typeof(ScriptBlock)),
|
|
PSGetMemberBinder.GetTargetExpr(args[1]).Convert(typeof(WhereOperatorSelectionMode)),
|
|
PSGetMemberBinder.GetTargetExpr(args[2]).Convert(typeof(int))),
|
|
lhsEnumerator.Restrictions.Merge(argRestrictions));
|
|
default:
|
|
{
|
|
// If the arity is wrong, throw the extended type system exception.
|
|
return new DynamicMetaObject(
|
|
Expression.Throw(
|
|
Expression.New(CachedReflectionInfo.MethodException_ctor,
|
|
Expression.Constant("MethodCountCouldNotFindBest"),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.MethodArgumentCountException),
|
|
Expression.NewArrayInit(typeof(object),
|
|
Expression.Constant(".Where({ expression } [, mode [, numberToReturn]])").Cast(typeof(object)),
|
|
ExpressionCache.Constant(args.Length).Cast(typeof(object)))),
|
|
this.ReturnType),
|
|
lhsEnumerator.Restrictions.Merge(argRestrictions));
|
|
}
|
|
}
|
|
}
|
|
|
|
private DynamicMetaObject InvokeForEachOnCollection(DynamicMetaObject targetEnumerator, DynamicMetaObject[] args, BindingRestrictions restrictions)
|
|
{
|
|
targetEnumerator = GetTargetAsEnumerable(targetEnumerator);
|
|
|
|
if (args.Length < 1)
|
|
{
|
|
// If the arity is wrong, throw the extended type system exception.
|
|
return new DynamicMetaObject(
|
|
Expression.Throw(
|
|
Expression.New(CachedReflectionInfo.MethodException_ctor,
|
|
Expression.Constant("MethodCountCouldNotFindBest"),
|
|
Expression.Constant(null, typeof(Exception)),
|
|
Expression.Constant(ExtendedTypeSystem.MethodArgumentCountException),
|
|
Expression.NewArrayInit(typeof(object),
|
|
Expression.Constant(".ForEach(expression [, arguments...])").Cast(typeof(object)),
|
|
ExpressionCache.Constant(args.Length).Cast(typeof(object)))),
|
|
this.ReturnType),
|
|
targetEnumerator.Restrictions.Merge(restrictions));
|
|
}
|
|
|
|
var lhsEnumerator = PSEnumerableBinder.IsEnumerable(targetEnumerator).Expression;
|
|
Expression argsToPass;
|
|
if (args.Length > 1)
|
|
{
|
|
argsToPass = Expression.NewArrayInit(typeof(object),
|
|
args.Skip(1).Select(static a => a.Expression.Cast(typeof(object))));
|
|
}
|
|
else
|
|
{
|
|
argsToPass = Expression.NewArrayInit(typeof(object));
|
|
}
|
|
|
|
return new DynamicMetaObject(
|
|
Expression.Call(CachedReflectionInfo.EnumerableOps_ForEach,
|
|
lhsEnumerator, PSGetMemberBinder.GetTargetExpr(args[0], typeof(object)), argsToPass),
|
|
targetEnumerator.Restrictions.Merge(restrictions));
|
|
}
|
|
|
|
#region Runtime helpers
|
|
|
|
internal static bool IsHomogenousArray<T>(object[] args)
|
|
{
|
|
if (args.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return args.All(element =>
|
|
{
|
|
var obj = PSObject.Base(element);
|
|
return obj != null && obj.GetType().Equals(typeof(T));
|
|
});
|
|
}
|
|
|
|
internal static bool IsHeterogeneousArray(object[] args)
|
|
{
|
|
if (args.Length == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var firstElement = PSObject.Base(args[0]);
|
|
if (firstElement == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var firstType = firstElement.GetType();
|
|
if (firstType.Equals(typeof(object)))
|
|
{
|
|
// When the effective argument type is object[], it's for one of 2 reasons
|
|
// * the array contains elements with different types
|
|
// * the array contains elements that are all object
|
|
// Arrays of all object is rare, which is why this method is called IsHeterogeneousArray,
|
|
// but we still want to correctly handle object[] full of objects, so we return true
|
|
// for that case as well.
|
|
return true;
|
|
}
|
|
|
|
return args.Skip(1).Any(element =>
|
|
{
|
|
var obj = PSObject.Base(element);
|
|
return obj == null || !firstType.Equals(obj.GetType());
|
|
});
|
|
}
|
|
|
|
internal static object InvokeAdaptedMember(object obj, string methodName, object[] args)
|
|
{
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable);
|
|
var methodInfo = adapterSet.OriginalAdapter.BaseGetMember<PSMemberInfo>(obj, methodName) as PSMethodInfo;
|
|
if (methodInfo == null && adapterSet.DotNetAdapter != null)
|
|
{
|
|
methodInfo = adapterSet.DotNetAdapter.BaseGetMember<PSMemberInfo>(obj, methodName) as PSMethodInfo;
|
|
}
|
|
|
|
if (methodInfo != null)
|
|
{
|
|
return methodInfo.Invoke(args);
|
|
}
|
|
|
|
// The object doesn't have 'Where' and 'ForEach' methods.
|
|
// As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like
|
|
// ([pscustomobject]@{ foo = 'bar' }).Foreach({$_})
|
|
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
|
|
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var enumerator = (new object[] { obj }).GetEnumerator();
|
|
switch (args.Length)
|
|
{
|
|
case 1:
|
|
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock, WhereOperatorSelectionMode.Default, 0);
|
|
case 2:
|
|
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock,
|
|
LanguagePrimitives.ConvertTo<WhereOperatorSelectionMode>(args[1]), 0);
|
|
case 3:
|
|
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock,
|
|
LanguagePrimitives.ConvertTo<WhereOperatorSelectionMode>(args[1]), LanguagePrimitives.ConvertTo<int>(args[2]));
|
|
}
|
|
}
|
|
|
|
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var enumerator = (new object[] { obj }).GetEnumerator();
|
|
return EnumerableOps.ForEach(enumerator, args[0], Array.Empty<object>());
|
|
}
|
|
|
|
throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), null,
|
|
"MethodNotFound", ParserStrings.MethodNotFound, ParserOps.GetTypeFullName(obj), methodName);
|
|
}
|
|
|
|
internal static object InvokeAdaptedSetMember(object obj, string methodName, object[] args, object valueToSet)
|
|
{
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable);
|
|
var methodInfo = adapterSet.OriginalAdapter.BaseGetMember<PSParameterizedProperty>(obj, methodName);
|
|
if (methodInfo == null && adapterSet.DotNetAdapter != null)
|
|
{
|
|
methodInfo = adapterSet.DotNetAdapter.BaseGetMember<PSParameterizedProperty>(obj, methodName);
|
|
}
|
|
|
|
if (methodInfo != null)
|
|
{
|
|
methodInfo.InvokeSet(valueToSet, args);
|
|
return valueToSet;
|
|
}
|
|
|
|
throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), null,
|
|
"MethodNotFound", ParserStrings.MethodNotFound, ParserOps.GetTypeFullName(obj), methodName);
|
|
}
|
|
|
|
internal static bool TryGetInstanceMethod(object value, string memberName, out PSMethodInfo methodInfo)
|
|
{
|
|
PSMemberInfoInternalCollection<PSMemberInfo> instanceMembers;
|
|
var memberInfo = PSObject.HasInstanceMembers(value, out instanceMembers) ? instanceMembers[memberName] : null;
|
|
methodInfo = memberInfo as PSMethodInfo;
|
|
if (memberInfo == null)
|
|
{
|
|
// No member, just return false
|
|
return false;
|
|
}
|
|
|
|
if (methodInfo == null)
|
|
{
|
|
// Found a member, but it wasn't a method, throw an exception because we can't call it.
|
|
throw InterpreterError.NewInterpreterException(memberName, typeof(RuntimeException), null,
|
|
"MethodNotFound", ParserStrings.MethodNotFound, ParserOps.GetTypeFullName(value), memberName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate regular binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (PSInvokeMemberBinder binder in s_binderCache.Values)
|
|
{
|
|
binder._getMemberBinder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
internal class PSCreateInstanceBinder : CreateInstanceBinder
|
|
{
|
|
private readonly CallInfo _callInfo;
|
|
private readonly PSMethodInvocationConstraints _constraints;
|
|
private readonly bool _publicTypeOnly;
|
|
private int _version;
|
|
|
|
private sealed class KeyComparer : IEqualityComparer<Tuple<CallInfo, PSMethodInvocationConstraints, bool>>
|
|
{
|
|
public bool Equals(Tuple<CallInfo, PSMethodInvocationConstraints, bool> x,
|
|
Tuple<CallInfo, PSMethodInvocationConstraints, bool> y)
|
|
{
|
|
return x.Item1.Equals(y.Item1)
|
|
&& ((x.Item2 == null) ? (y.Item2 == null) : x.Item2.Equals(y.Item2))
|
|
&& x.Item3 == y.Item3;
|
|
}
|
|
|
|
public int GetHashCode(Tuple<CallInfo, PSMethodInvocationConstraints, bool> obj)
|
|
{
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
|
|
private static readonly
|
|
Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints, bool>, PSCreateInstanceBinder>
|
|
s_binderCache =
|
|
new Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints, bool>, PSCreateInstanceBinder>(new KeyComparer());
|
|
|
|
public static PSCreateInstanceBinder Get(CallInfo callInfo, PSMethodInvocationConstraints constraints, bool publicTypeOnly = false)
|
|
{
|
|
PSCreateInstanceBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
var key = Tuple.Create(callInfo, constraints, publicTypeOnly);
|
|
if (!s_binderCache.TryGetValue(key, out result))
|
|
{
|
|
result = new PSCreateInstanceBinder(callInfo, constraints, publicTypeOnly);
|
|
s_binderCache.Add(key, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal static void InvalidateCache()
|
|
{
|
|
// Invalidate binders
|
|
lock (s_binderCache)
|
|
{
|
|
foreach (var binder in s_binderCache.Values)
|
|
{
|
|
binder._version += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal PSCreateInstanceBinder(CallInfo callInfo, PSMethodInvocationConstraints constraints, bool publicTypeOnly)
|
|
: base(callInfo)
|
|
{
|
|
_publicTypeOnly = publicTypeOnly;
|
|
_callInfo = callInfo;
|
|
_constraints = constraints;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(CultureInfo.InvariantCulture,
|
|
"PSCreateInstanceBinder: ver:{0} args:{1} constraints:<{2}>", _version, _callInfo.ArgumentCount, _constraints != null ? _constraints.ToString() : string.Empty);
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || args.Any(static arg => !arg.HasValue))
|
|
{
|
|
return Defer(args.Prepend(target).ToArray());
|
|
}
|
|
|
|
var targetValue = PSObject.Base(target.Value);
|
|
if (targetValue == null)
|
|
{
|
|
return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "InvokeMethodOnNull", ParserStrings.InvokeMethodOnNull).WriteToDebugLog(this);
|
|
}
|
|
|
|
var instanceType = targetValue as Type ?? targetValue.GetType();
|
|
|
|
BindingRestrictions restrictions;
|
|
if (instanceType.IsByRefLike)
|
|
{
|
|
// ByRef-like types are not boxable and should be used only on stack
|
|
restrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.PSCreateInstanceBinder_IsTargetTypeByRefLike, target.Expression));
|
|
|
|
return target.ThrowRuntimeError(
|
|
restrictions,
|
|
nameof(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType),
|
|
ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType,
|
|
Expression.Call(
|
|
CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName,
|
|
target.Expression)).WriteToDebugLog(this);
|
|
}
|
|
|
|
if (_publicTypeOnly && !TypeResolver.IsPublic(instanceType))
|
|
{
|
|
// If 'publicTypeOnly' specified, we only support creating instance for public types.
|
|
restrictions = BindingRestrictions.GetExpressionRestriction(
|
|
Expression.Call(CachedReflectionInfo.PSCreateInstanceBinder_IsTargetTypeNonPublic, target.Expression));
|
|
|
|
return target.ThrowRuntimeError(
|
|
restrictions,
|
|
nameof(ParserStrings.MethodNotFound),
|
|
ParserStrings.MethodNotFound,
|
|
Expression.Call(
|
|
CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName,
|
|
target.Expression),
|
|
Expression.Constant("new")).WriteToDebugLog(this);
|
|
}
|
|
|
|
var ctors = instanceType.GetConstructors();
|
|
restrictions = ReferenceEquals(instanceType, targetValue)
|
|
? (target.Value is PSObject)
|
|
? BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression), instanceType)
|
|
: BindingRestrictions.GetInstanceRestriction(target.Expression, instanceType)
|
|
: target.PSGetTypeRestriction();
|
|
restrictions = restrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, instanceType, _version));
|
|
if (ctors.Length == 0 && _callInfo.ArgumentCount == 0 && instanceType.IsValueType)
|
|
{
|
|
// No ctors, just call the default ctor
|
|
return new DynamicMetaObject(Expression.New(instanceType).Cast(this.ReturnType), restrictions).WriteToDebugLog(this);
|
|
}
|
|
|
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
|
if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage && !CoreTypes.Contains(instanceType))
|
|
{
|
|
return target.ThrowRuntimeError(restrictions, "CannotCreateTypeConstrainedLanguage", ParserStrings.CannotCreateTypeConstrainedLanguage).WriteToDebugLog(this);
|
|
}
|
|
|
|
restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
|
|
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors);
|
|
return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.Ordinary,
|
|
target, args, restrictions, newConstructors, typeof(MethodException)).WriteToDebugLog(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the target type is ByRef-like.
|
|
/// </summary>
|
|
internal static bool IsTargetTypeByRefLike(object target)
|
|
{
|
|
var targetValue = PSObject.Base(target);
|
|
if (targetValue == null) { return false; }
|
|
|
|
var instanceType = targetValue as Type ?? targetValue.GetType();
|
|
return instanceType.IsByRefLike;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the target type is not public.
|
|
/// </summary>
|
|
internal static bool IsTargetTypeNonPublic(object target)
|
|
{
|
|
var targetValue = PSObject.Base(target);
|
|
if (targetValue == null) { return false; }
|
|
|
|
var instanceType = targetValue as Type ?? targetValue.GetType();
|
|
return !TypeResolver.IsPublic(instanceType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the full name of the target type.
|
|
/// </summary>
|
|
internal static string GetTargetTypeName(object target)
|
|
{
|
|
var targetValue = PSObject.Base(target);
|
|
Diagnostics.Assert(targetValue != null, "caller makes sure target is not null");
|
|
|
|
var instanceType = targetValue as Type ?? targetValue.GetType();
|
|
return instanceType.FullName;
|
|
}
|
|
}
|
|
|
|
internal class PSInvokeBaseCtorBinder : InvokeMemberBinder
|
|
{
|
|
private readonly CallInfo _callInfo;
|
|
private readonly PSMethodInvocationConstraints _constraints;
|
|
|
|
private sealed class KeyComparer : IEqualityComparer<Tuple<CallInfo, PSMethodInvocationConstraints>>
|
|
{
|
|
public bool Equals(Tuple<CallInfo, PSMethodInvocationConstraints> x,
|
|
Tuple<CallInfo, PSMethodInvocationConstraints> y)
|
|
{
|
|
return x.Item1.Equals(y.Item1)
|
|
&& ((x.Item2 == null) ? (y.Item2 == null) : x.Item2.Equals(y.Item2));
|
|
}
|
|
|
|
public int GetHashCode(Tuple<CallInfo, PSMethodInvocationConstraints> obj)
|
|
{
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
|
|
private static readonly
|
|
Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints>, PSInvokeBaseCtorBinder>
|
|
s_binderCache =
|
|
new Dictionary<Tuple<CallInfo, PSMethodInvocationConstraints>, PSInvokeBaseCtorBinder>(new KeyComparer());
|
|
|
|
public static PSInvokeBaseCtorBinder Get(CallInfo callInfo, PSMethodInvocationConstraints constraints)
|
|
{
|
|
PSInvokeBaseCtorBinder result;
|
|
|
|
lock (s_binderCache)
|
|
{
|
|
var key = Tuple.Create(callInfo, constraints);
|
|
if (!s_binderCache.TryGetValue(key, out result))
|
|
{
|
|
result = new PSInvokeBaseCtorBinder(callInfo, constraints);
|
|
s_binderCache.Add(key, result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal PSInvokeBaseCtorBinder(CallInfo callInfo, PSMethodInvocationConstraints constraints)
|
|
: base(".ctor", false, callInfo)
|
|
{
|
|
_callInfo = callInfo;
|
|
_constraints = constraints;
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
if (!target.HasValue || args.Any(static arg => !arg.HasValue))
|
|
{
|
|
return Defer(args.Prepend(target).ToArray());
|
|
}
|
|
|
|
var ctors = _constraints.MethodTargetType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
var restrictions = target.Value is PSObject
|
|
? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType())
|
|
: target.PSGetTypeRestriction();
|
|
restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
|
|
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily).ToArray());
|
|
return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.BaseCtor,
|
|
target, args, restrictions, newConstructors, typeof(MethodException));
|
|
}
|
|
|
|
public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
|
|
{
|
|
return (errorSuggestion ?? new DynamicMetaObject(
|
|
DynamicExpression.Dynamic(
|
|
new PSInvokeBinder(CallInfo),
|
|
typeof(object),
|
|
args.Prepend(target).Select(static dmo => dmo.Expression)
|
|
),
|
|
target.Restrictions.Merge(BindingRestrictions.Combine(args))
|
|
));
|
|
}
|
|
}
|
|
|
|
#endregion Standard binders
|
|
}
|