Support calling method with ByRef-like type parameters (#7721)

Support calling methods with ByRef-like type parameters in PowerShell, as long as the argument specified for the parameter can be implicitly/explicitly converted to the ByRef-like type.

We cannot create an instance of a ByRef-like type in PowerShell, but there are types that can be implicitly or explicitly converted to ByRef-like types, such as `T[] -> Span<T>`, `T[] -> ReadOnlySpan<T>`, `String -> ReadOnlySpan<T>`. `ArraySegment<T> -> Span<T>`, `ArraySegment<T> -> ReadOnlySpan<T>`. With changes in this PR, we can call methods with ByRef-like parameter types by providing the arguments of the types that can be cast to the target ByRef-like type.

**What is enabled?**
1. Invoking methods that have ByRef-like parameters, but the return type is not ByRef-like.
2. Invoking constructors with ByRef-like parameters via `[target-type]::new` syntax, but the `target-type` is not ByRef-like.
3. Accessing indexers that have ByRef-like parameters, but the return type is not ByRef-like.
This commit is contained in:
Dongbo Wang 2018-09-11 16:57:35 -07:00 committed by GitHub
parent 131f1f9efb
commit 720e842c86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 411 additions and 94 deletions

View file

@ -708,28 +708,46 @@ namespace System.Management.Automation
#region Internal Helper Methods
private static Type GetArgumentType(object argument)
private static Type GetArgumentType(object argument, bool isByRefParameter)
{
if (argument == null)
{
return typeof(LanguagePrimitives.Null);
}
PSReference psref = argument as PSReference;
if (psref != null)
if (isByRefParameter && argument is PSReference psref)
{
return GetArgumentType(PSObject.Base(psref.Value));
return GetArgumentType(PSObject.Base(psref.Value), isByRefParameter: false);
}
return argument.GetType();
}
internal static ConversionRank GetArgumentConversionRank(object argument, Type parameterType)
internal static ConversionRank GetArgumentConversionRank(object argument, Type parameterType, bool isByRef, bool allowCastingToByRefLikeType)
{
Type fromType = GetArgumentType(argument);
ConversionRank rank = LanguagePrimitives.GetConversionRank(fromType, parameterType);
Type fromType = null;
ConversionRank rank = ConversionRank.None;
if (allowCastingToByRefLikeType && parameterType.IsByRefLike)
{
// When resolving best method for use in binders, we can accept implicit/explicit casting conversions to
// a ByRef-like target type, because when generating IL from a call site with the binder, the IL includes
// the casting operation. However, we don't accept such conversions when it's for invoking the method via
// reflection, because reflection just doesn't support ByRef-like type.
fromType = GetArgumentType(PSObject.Base(argument), isByRefParameter: false);
if (fromType != typeof(LanguagePrimitives.Null))
{
LanguagePrimitives.FigureCastConversion(fromType, parameterType, ref rank);
}
return rank;
}
fromType = GetArgumentType(argument, isByRef);
rank = LanguagePrimitives.GetConversionRank(fromType, parameterType);
if (rank == ConversionRank.None)
{
fromType = GetArgumentType(PSObject.Base(argument));
fromType = GetArgumentType(PSObject.Base(argument), isByRef);
rank = LanguagePrimitives.GetConversionRank(fromType, parameterType);
}
@ -1186,6 +1204,7 @@ namespace System.Management.Automation
/// </summary>
/// <param name="methods">different overloads for a method</param>
/// <param name="invocationConstraints">invocation constraints</param>
/// <param name="allowCastingToByRefLikeType">true if we accept implicit/explicit casting conversion to a ByRef-like parameter type for method resolution</param>
/// <param name="arguments">arguments to check against the overloads</param>
/// <param name="errorId">if no best method, the error id to use in the error message</param>
/// <param name="errorMsg">if no best method, the error message (format string) to use in the error message</param>
@ -1194,6 +1213,7 @@ namespace System.Management.Automation
internal static MethodInformation FindBestMethod(
MethodInformation[] methods,
PSMethodInvocationConstraints invocationConstraints,
bool allowCastingToByRefLikeType,
object[] arguments,
ref string errorId,
ref string errorMsg,
@ -1201,7 +1221,7 @@ namespace System.Management.Automation
out bool callNonVirtually)
{
callNonVirtually = false;
var methodInfo = FindBestMethodImpl(methods, invocationConstraints, arguments, ref errorId, ref errorMsg, out expandParamsOnBest);
var methodInfo = FindBestMethodImpl(methods, invocationConstraints, allowCastingToByRefLikeType, arguments, ref errorId, ref errorMsg, out expandParamsOnBest);
if (methodInfo == null)
{
return null;
@ -1243,6 +1263,7 @@ namespace System.Management.Automation
private static MethodInformation FindBestMethodImpl(
MethodInformation[] methods,
PSMethodInvocationConstraints invocationConstraints,
bool allowCastingToByRefLikeType,
object[] arguments,
ref string errorId,
ref string errorMsg,
@ -1364,8 +1385,18 @@ namespace System.Management.Automation
Type elementType = parameter.parameterType.GetElementType();
if (parameters.Length == arguments.Length)
{
ConversionRank arrayConv = GetArgumentConversionRank(arguments[j], parameter.parameterType);
ConversionRank elemConv = GetArgumentConversionRank(arguments[j], elementType);
ConversionRank arrayConv = GetArgumentConversionRank(
arguments[j],
parameter.parameterType,
isByRef: false,
allowCastingToByRefLikeType: false);
ConversionRank elemConv = GetArgumentConversionRank(
arguments[j],
elementType,
isByRef: false,
allowCastingToByRefLikeType: false);
if (elemConv > arrayConv)
{
candidate.expandedParameters = ExpandParameters(arguments.Length, parameters, elementType);
@ -1387,7 +1418,12 @@ namespace System.Management.Automation
// Note that we go through here when the param array parameter has no argument.
for (int k = j; k < arguments.Length; k++)
{
candidate.conversionRanks[k] = GetArgumentConversionRank(arguments[k], elementType);
candidate.conversionRanks[k] = GetArgumentConversionRank(
arguments[k],
elementType,
isByRef: false,
allowCastingToByRefLikeType: false);
if (candidate.conversionRanks[k] == ConversionRank.None)
{
// No longer a candidate
@ -1404,7 +1440,11 @@ namespace System.Management.Automation
}
else
{
candidate.conversionRanks[j] = GetArgumentConversionRank(arguments[j], parameter.parameterType);
candidate.conversionRanks[j] = GetArgumentConversionRank(
arguments[j],
parameter.parameterType,
parameter.isByRef,
allowCastingToByRefLikeType);
if (candidate.conversionRanks[j] == ConversionRank.None)
{
@ -1540,7 +1580,17 @@ namespace System.Management.Automation
bool callNonVirtually;
string errorId = null;
string errorMsg = null;
MethodInformation bestMethod = FindBestMethod(methods, invocationConstraints, arguments, ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually);
MethodInformation bestMethod = FindBestMethod(
methods,
invocationConstraints,
allowCastingToByRefLikeType: false,
arguments,
ref errorId,
ref errorMsg,
out expandParamsOnBest,
out callNonVirtually);
if (bestMethod == null)
{
throw new MethodException(errorId, null, errorMsg, methodName, arguments.Length);

View file

@ -1410,6 +1410,7 @@ namespace System.Management.Automation
#region type converter
internal static PSTraceSource typeConversion = PSTraceSource.GetTracer("TypeConversion", "Traces the type conversion algorithm", false);
internal static ConversionData<object> NoConversion = new ConversionData<object>(ConvertNoConversion, ConversionRank.None);
private static TypeConverter GetIntegerSystemConverter(Type type)
{

View file

@ -70,7 +70,17 @@ namespace System.Management.Automation.Language
bool expandParamsOnBest;
bool callNonVirtually;
var positionalArgCount = positionalArgs.Length;
var bestMethod = Adapter.FindBestMethod(newConstructors, null, positionalArgs, ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually);
var bestMethod = Adapter.FindBestMethod(
newConstructors,
invocationConstraints: null,
allowCastingToByRefLikeType: false,
positionalArgs,
ref errorId,
ref errorMsg,
out expandParamsOnBest,
out callNonVirtually);
if (bestMethod == null)
{
parser.ReportError(new ParseError(attributeAst.Extent, errorId,

View file

@ -247,6 +247,7 @@ namespace System.Management.Automation.Language
Type parameterType,
string parameterName,
string methodName,
bool allowCastingToByRefLikeType,
List<ParameterExpression> temps,
List<Expression> initTemps)
{
@ -267,13 +268,26 @@ namespace System.Management.Automation.Language
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;
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 = LanguagePrimitives.FigureConversion(target.Value, parameterType, out debase);
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 },
@ -1621,8 +1635,17 @@ namespace System.Management.Automation.Language
bool expandParamsOnBest;
bool callNonVirtually;
var positionalArgCount = CallInfo.ArgumentCount - CallInfo.ArgumentNames.Count;
var bestMethod = Adapter.FindBestMethod(newConstructors, null,
args.Take(positionalArgCount).Select(arg => arg.Value).ToArray(), ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually);
var bestMethod = Adapter.FindBestMethod(
newConstructors,
invocationConstraints: null,
allowCastingToByRefLikeType: false,
args.Take(positionalArgCount).Select(arg => arg.Value).ToArray(),
ref errorId,
ref errorMsg,
out expandParamsOnBest,
out callNonVirtually);
if (bestMethod == null)
{
return errorSuggestion ?? new DynamicMetaObject(
@ -1676,14 +1699,7 @@ namespace System.Management.Automation.Language
}
else
{
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, resultType, out debase);
Diagnostics.Assert(conversion.Rank != ConversionRank.None, "FindBestMethod should have failed if there is no conversion");
var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, resultType, out bool debase);
ctorArgs[argIndex] = PSConvertBinder.InvokeConverter(conversion, args[argIndex].Expression, resultType, debase, ExpressionCache.InvariantCulture);
}
}
@ -3771,6 +3787,39 @@ namespace System.Management.Automation.Language
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 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.ConversionData conversion,
Expression value,
Type resultType,
@ -4270,22 +4319,9 @@ namespace System.Management.Automation.Language
}
}
Expression[] indexExprs = new Expression[getterParams.Length];
for (int i = 0; i < getterParams.Length; ++i)
{
var parameterType = getterParams[i].ParameterType;
indexExprs[i] = 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);
}
}
// Check return type after the argument conversion, so any no-conversion error can be thrown.
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(
@ -4296,7 +4332,22 @@ namespace System.Management.Automation.Language
Expression.Constant(target.LimitType, typeof(Type)),
Expression.Constant(getter.ReturnType, typeof(Type)))),
GetNullResult()),
target.CombineRestrictions(indexes));
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))
@ -4333,10 +4384,8 @@ namespace System.Management.Automation.Language
internal static Expression ConvertIndex(DynamicMetaObject index, Type resultType)
{
bool debase;
// ConstrainedLanguage note - Calls to this conversion are protected by the binding rules that call it.
var conversion = LanguagePrimitives.FigureConversion(index.Value, resultType, out debase);
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);
@ -4543,37 +4592,38 @@ namespace System.Management.Automation.Language
}
var setterParams = setter.GetParameters();
if (setterParams.Length != indexes.Length + 1)
{
#if false
if (setterParams.Length == 1 && _allowSlicing)
{
// We have a slicing operation.
return InvokeSlicingIndexer(target, indexes);
}
#endif
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 false
if (setterParams.Length == 1)
if (setterParams[paramLength - 1].ParameterType.IsByRefLike)
{
// The getter takes a single argument, so first check if we're slicing.
var slicingResult = CheckForSlicing(target, indexes);
if (slicingResult != null)
{
return slicingResult;
}
// 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());
}
#endif
Expression[] indexExprs = new Expression[setterParams.Length];
for (int i = 0; i < setterParams.Length; ++i)
Expression[] indexExprs = new Expression[paramLength];
for (int i = 0; i < paramLength; ++i)
{
var parameterType = setterParams[i].ParameterType;
indexExprs[i] = PSGetIndexBinder.ConvertIndex(i == setterParams.Length - 1 ? value : indexes[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.
@ -4581,7 +4631,7 @@ namespace System.Management.Automation.Language
}
}
if (setterParams.Length == 2 && setterParams[0].ParameterType == typeof(int) && !(target.Value is IDictionary))
if (paramLength == 2 && setterParams[0].ParameterType == typeof(int) && !(target.Value is 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
@ -6110,6 +6160,9 @@ namespace System.Management.Automation.Language
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,
@ -6227,13 +6280,38 @@ namespace System.Management.Automation.Language
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(codeProperty.SetterCodeReference, null, new[] { target, value },
false, PSInvokeMemberBinder.MethodInvocationType.Setter),
PSInvokeMemberBinder.InvokeMethod(
setterMethod,
target: null,
new[] { target, value },
expandParameters: false,
PSInvokeMemberBinder.MethodInvocationType.Setter),
temp),
restrictions).WriteToDebugLog(this);
}
@ -6743,8 +6821,16 @@ namespace System.Management.Automation.Language
psMethodInvocationConstraints = new PSMethodInvocationConstraints(target.Value.GetType(), argTypes);
}
var result = Adapter.FindBestMethod(mi, psMethodInvocationConstraints,
argValues, ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually);
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;
@ -6834,9 +6920,17 @@ namespace System.Management.Automation.Language
string errorMsg = null;
bool expandParameters;
bool callNonVirtually;
var mi = Adapter.FindBestMethod(data.methodInformationStructures, invocationConstraints,
args.Select(arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(),
ref errorId, ref errorMsg, out expandParameters, out callNonVirtually);
var mi = Adapter.FindBestMethod(
data.methodInformationStructures,
invocationConstraints,
allowCastingToByRefLikeType: true,
args.Select(arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(),
ref errorId,
ref errorMsg,
out expandParameters,
out callNonVirtually);
if (mi != null)
{
result = (MethodInfo)mi.method;
@ -6845,7 +6939,7 @@ namespace System.Management.Automation.Language
return result;
}
internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, DynamicMetaObject[] args, bool expandParameters, MethodInvocationType invocationInvocationType)
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>();
@ -6858,9 +6952,23 @@ namespace System.Management.Automation.Language
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(
CachedReflectionInfo.MethodException_ctor,
exceptionCtorInfo,
Expression.Constant(nameof(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType)),
Expression.Constant(null, typeof(Exception)),
Expression.Constant(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType),
@ -6890,12 +6998,20 @@ namespace System.Management.Automation.Language
}
}
// 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(parameters[i].Name))
if (string.IsNullOrWhiteSpace(paramName))
{
paramName = i.ToString(CultureInfo.InvariantCulture);
}
@ -6910,17 +7026,30 @@ namespace System.Management.Automation.Language
if (paramArrayAttrs != null && paramArrayAttrs.Any())
{
Diagnostics.Assert(i == parameters.Length - 1, "vararg parameter is not the last");
var paramElementType = parameters[i].ParameterType.GetElementType();
var paramElementType = parameterType.GetElementType();
if (expandParameters)
{
argExprs[i] = Expression.NewArrayInit(paramElementType,
args.Skip(i).Select(
a => a.CastOrConvertMethodArgument(paramElementType, paramName, mi.Name, temps, initTemps)));
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(parameters[i].ParameterType, paramName, mi.Name, temps, initTemps);
var arg = args[i].CastOrConvertMethodArgument(
parameterType,
paramName,
mi.Name,
allowCastingToByRefLikeType: false,
temps,
initTemps);
argExprs[i] = arg;
}
}
@ -6931,7 +7060,7 @@ namespace System.Management.Automation.Language
var argValue = parameters[i].DefaultValue;
if (argValue == null)
{
argExprs[i] = Expression.Default(parameters[i].ParameterType);
argExprs[i] = Expression.Default(parameterType);
}
else
{
@ -6943,7 +7072,7 @@ namespace System.Management.Automation.Language
}
else
{
if (parameters[i].ParameterType.IsByRef)
if (parameterType.IsByRef)
{
if (!(args[i].Value is PSReference))
{
@ -6953,7 +7082,7 @@ namespace System.Management.Automation.Language
new object[] { i + 1, typeof(PSReference).FullName, "[ref]" });
}
var temp = Expression.Variable(parameters[i].ParameterType.GetElementType());
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)));
@ -6962,7 +7091,14 @@ namespace System.Management.Automation.Language
}
else
{
argExprs[i] = args[i].CastOrConvertMethodArgument(parameters[i].ParameterType, paramName, mi.Name, temps, initTemps);
argExprs[i] = args[i].CastOrConvertMethodArgument(
parameterType,
paramName,
mi.Name,
allowCastingToByRefLikeType,
temps,
initTemps);
}
}
}
@ -6970,9 +7106,9 @@ namespace System.Management.Automation.Language
Expression call;
if (constructorInfo != null)
{
if (invocationInvocationType == MethodInvocationType.BaseCtor)
if (invocationType == MethodInvocationType.BaseCtor)
{
var targetExpr = target.Value is PSObject
var targetExpr = target.Value is PSObject
? target.Expression.Cast(constructorInfo.DeclaringType)
: PSGetMemberBinder.GetTargetExpr(target, constructorInfo.DeclaringType);
call = Expression.Call(
@ -6988,7 +7124,7 @@ namespace System.Management.Automation.Language
}
else
{
if (invocationInvocationType == MethodInvocationType.NonVirtual && !methodInfo.IsStatic)
if (invocationType == MethodInvocationType.NonVirtual && !methodInfo.IsStatic)
{
call = Expression.Call(
methodInfo.ReturnType == typeof(void)

View file

@ -6,6 +6,7 @@ Describe "Handle ByRef-like types gracefully" -Tags "CI" {
BeforeAll {
$code = @'
using System;
using System.Management.Automation;
namespace DotNetInterop
{
public class Test
@ -32,17 +33,60 @@ namespace DotNetInterop
{
}
public string PrintMySpan(string str, Span<int> mySpan = default)
public string PrintMySpan(string str, ReadOnlySpan<char> mySpan = default)
{
return str;
if (mySpan.Length == 0)
{
return str;
}
else
{
return str + mySpan.Length;
}
}
public Span<int> GetSpan(int[] array)
{
return array.AsSpan();
}
}
public class Test2
{
public Test2(ReadOnlySpan<char> span)
{
name = $"Number of chars: {span.Length}";
}
public Test2() {}
private string name = "Hello World";
public string this[ReadOnlySpan<char> span]
{
get { return name; }
set { name = value; }
}
public string Name => name;
}
public class CodeMethods
{
public static ReadOnlySpan<char> GetProperty(PSObject instance)
{
return default(ReadOnlySpan<char>);
}
public static void SetProperty(PSObject instance, ReadOnlySpan<char> span)
{
}
public static string RunMethod(PSObject instance, string str, ReadOnlySpan<char> span)
{
return str + span.Length;
}
}
public ref struct MyByRefLikeType
{
public MyByRefLikeType(int i) { }
@ -56,6 +100,7 @@ namespace DotNetInterop
}
$testObj = [DotNetInterop.Test]::new()
$test2Obj = [DotNetInterop.Test2]::new()
}
It "New-Object should fail gracefully when used for a ByRef-like type" {
@ -180,7 +225,82 @@ namespace DotNetInterop
}
}
It "Set access of an indexer that accepts ByRef-like type should fail gracefully" {
{ $testObj[1] = 1 } | Should -Throw -ErrorId "InvalidCastToByRefLikeType"
It "Set access of an indexer that accepts ByRef-like type value should fail gracefully" {
{ $testObj[1] = 1 } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType"
}
Context "Passing value that is implicitly/explicitly castable to ByRef-like parameter in method invocation" {
BeforeAll {
$ps = [powershell]::Create()
# Define the CodeMethod 'RunTest'
$ps.AddCommand("Update-TypeData").
AddParameter("TypeName", "DotNetInterop.Test2").
AddParameter("MemberType", "CodeMethod").
AddParameter("MemberName", "RunTest").
AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('RunMethod')).Invoke()
$ps.Commands.Clear()
# Define the CodeProperty 'TestName'
$ps.AddCommand("Update-TypeData").
AddParameter("TypeName", "DotNetInterop.Test2").
AddParameter("MemberType", "CodeProperty").
AddParameter("MemberName", "TestName").
AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('GetProperty')).
AddParameter("SecondValue", [DotNetInterop.CodeMethods].GetMethod('SetProperty')).Invoke()
$ps.Commands.Clear()
$ps.AddScript('$test = [DotNetInterop.Test2]::new()').Invoke()
$ps.Commands.Clear()
}
AfterAll {
$ps.Dispose()
}
It "Support method calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
$testObj.PrintMySpan("abc", "def") | Should -BeExactly "abc3"
$testObj.PrintMySpan("abc", "Hello".ToCharArray()) | Should -BeExactly "abc5"
{ $testObj.PrintMySpan("abc", 12) } | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument"
$path = [System.IO.Path]::GetTempPath()
[System.IO.Path]::IsPathRooted($path.ToCharArray()) | Should -Be $true
}
It "Support constructor calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
$result = [DotNetInterop.Test2]::new("abc")
$result.Name | Should -BeExactly "Number of chars: 3"
{ [DotNetInterop.Test2]::new(12) } | Should -Throw -ErrorId "MethodCountCouldNotFindBest"
}
It "Support indexing operation with ByRef-like index as long as the argument can be casted to the ByRef-like type" {
$test2Obj["abc"] | Should -BeExactly "Hello World"
$test2Obj["abc"] = "pwsh"
$test2Obj["abc"] | Should -BeExactly "pwsh"
}
It "Support CodeMethod with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
$result = $ps.AddScript('$test.RunTest("Hello", "World".ToCharArray())').Invoke()
$ps.Commands.Clear()
$result.Count | Should -Be 1
$result[0] | Should -Be 'Hello5'
}
It "Return null for getter access of a CodeProperty that returns a ByRef-like type, even in strict mode" {
$result = $ps.AddScript(
'try { Set-StrictMode -Version latest; $test.TestName } finally { Set-StrictMode -Off }').Invoke()
$ps.Commands.Clear()
$result.Count | Should -Be 1
$result[0] | Should -Be $null
}
It "Fail gracefully for setter access of a CodeProperty that returns a ByRef-like type" {
$result = $ps.AddScript('$test.TestName = "Hello"').Invoke()
$ps.Commands.Clear()
$result.Count | Should -Be 0
$ps.Streams.Error[0].FullyQualifiedErrorId | Should -Be "CannotAccessByRefLikePropertyOrField"
}
}
}