diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs index a9b6280b8b..6d42a50acc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs @@ -947,6 +947,7 @@ namespace Microsoft.PowerShell.Commands } else { + parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); compilationOptions = GetDefaultCompilationOptions(); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs index e17d2d85dd..99fc94e0dc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs @@ -143,27 +143,42 @@ namespace Microsoft.PowerShell.Commands if (e.InnerException != null && e.InnerException is TypeResolver.AmbiguousTypeException) { ThrowTerminatingError( - new ErrorRecord( - e, - "AmbiguousTypeReference", - ErrorCategory.InvalidType, null)); + new ErrorRecord( + e, + "AmbiguousTypeReference", + ErrorCategory.InvalidType, + targetObject: null)); } mshArgE = PSTraceSource.NewArgumentException( - "TypeName", - NewObjectStrings.TypeNotFound, - TypeName); + "TypeName", + NewObjectStrings.TypeNotFound, + TypeName); + ThrowTerminatingError( new ErrorRecord( mshArgE, "TypeNotFound", - ErrorCategory.InvalidType, null)); + ErrorCategory.InvalidType, + targetObject: null)); } throw e; } Diagnostics.Assert(type != null, "LanguagePrimitives.TryConvertTo failed but returned true"); + if (type.IsByRefLike) + { + ThrowTerminatingError( + new ErrorRecord( + PSTraceSource.NewInvalidOperationException( + NewObjectStrings.CannotInstantiateBoxedByRefLikeType, + type), + nameof(NewObjectStrings.CannotInstantiateBoxedByRefLikeType), + ErrorCategory.InvalidOperation, + targetObject: null)); + } + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) { if (!CoreTypes.Contains(type)) diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx index cce49c375f..a435ee62db 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx @@ -138,6 +138,9 @@ Creating instances of attribute and delegated Windows RT types is not supported. + + Cannot create instances of the ByRef-like type "{0}". ByRef-like types are not supported in PowerShell. + Cannot create type. Only core types are supported in this language mode. diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index 6904170d13..8cc4abe4ed 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -1920,6 +1920,38 @@ namespace System.Management.Automation internal object Invoke(object target, object[] arguments) { + // There may be parameters of ByRef-like types, but they will be taken care of + // when we resolve overloads to find the best methods -- proper exception will + // be thrown when converting arguments to the ByRef-like parameter types. + // + // So when reaching here, we only care about (1) if the method return type is + // BeRef-like; (2) if it's a constrcutor of a ByRef-like type. + + if (method is ConstructorInfo ctor) + { + if (ctor.DeclaringType.IsByRefLike) + { + throw new MethodException( + nameof(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType), + innerException: null, + ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType, + ctor.DeclaringType); + } + + return ctor.Invoke(arguments); + } + + var methodInfo = (MethodInfo) method; + if (methodInfo.ReturnType.IsByRefLike) + { + throw new MethodException( + nameof(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType), + innerException: null, + ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType, + methodInfo.Name, + methodInfo.ReturnType); + } + if (target is PSObject) { if (!method.DeclaringType.IsAssignableFrom(target.GetType())) @@ -1932,20 +1964,15 @@ namespace System.Management.Automation { if (_methodInvoker == null) { - if (!(method is MethodInfo)) - { - _useReflection = true; - } - else - { - _methodInvoker = GetMethodInvoker((MethodInfo)method); - } + _methodInvoker = GetMethodInvoker(methodInfo); } + if (_methodInvoker != null) { return _methodInvoker(target, arguments); } } + return method.Invoke(target, arguments); } @@ -2455,13 +2482,12 @@ namespace System.Management.Automation // require different delegates // The same is true for generics, COM Types. Type declaringType = property.DeclaringType; - Type propertyType = property.PropertyType; if (declaringType.IsValueType || propertyType.IsGenericType || declaringType.IsGenericType || - property.DeclaringType.IsCOMObject || - property.PropertyType.IsCOMObject) + declaringType.IsCOMObject || + propertyType.IsCOMObject) { this.readOnly = property.GetSetMethod() == null; this.writeOnly = property.GetGetMethod() == null; @@ -2511,7 +2537,9 @@ namespace System.Management.Automation private void InitGetter() { if (writeOnly || useReflection) + { return; + } var parameter = Expression.Parameter(typeof(object)); Expression instance = null; @@ -2568,7 +2596,9 @@ namespace System.Management.Automation private void InitSetter() { if (readOnly || useReflection) + { return; + } var parameter = Expression.Parameter(typeof(object)); var value = Expression.Parameter(typeof(object)); @@ -3621,16 +3651,29 @@ namespace System.Management.Automation protected override object PropertyGet(PSProperty property) { PropertyCacheEntry adapterData = (PropertyCacheEntry)property.adapterData; + + if (adapterData.propertyType.IsByRefLike) + { + throw new GetValueException( + nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField), + innerException: null, + ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField, + adapterData.member.Name, + adapterData.propertyType); + } + PropertyInfo propertyInfo = adapterData.member as PropertyInfo; if (propertyInfo != null) { if (adapterData.writeOnly) { - throw new GetValueException("WriteOnlyProperty", - null, + throw new GetValueException( + nameof(ExtendedTypeSystem.WriteOnlyProperty), + innerException: null, ExtendedTypeSystem.WriteOnlyProperty, propertyInfo.Name); } + if (adapterData.useReflection) { return propertyInfo.GetValue(property.baseObject, null); @@ -3664,12 +3707,23 @@ namespace System.Management.Automation if (adapterData.readOnly) { - throw new SetValueException(nameof(ExtendedTypeSystem.ReadOnlyProperty), - null, + throw new SetValueException( + nameof(ExtendedTypeSystem.ReadOnlyProperty), + innerException: null, ExtendedTypeSystem.ReadOnlyProperty, adapterData.member.Name); } + if (adapterData.propertyType.IsByRefLike) + { + throw new SetValueException( + nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField), + innerException: null, + ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField, + adapterData.member.Name, + adapterData.propertyType); + } + PropertyInfo propertyInfo = adapterData.member as PropertyInfo; if (propertyInfo != null) { @@ -3755,9 +3809,7 @@ namespace System.Management.Automation #pragma warning disable 56500 try { - // We cannot call MethodBase's Invoke on a constructor - // because it requires a target we don't have. - returnValue = ((ConstructorInfo)methodInformation.method).Invoke(arguments); + returnValue = methodInformation.Invoke(target: null, arguments); } catch (TargetInvocationException ex) { diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 69b0a9d62b..0c954de68d 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -4243,8 +4243,7 @@ namespace System.Management.Automation } } - private static Dictionary s_converterCache - = new Dictionary(256); + private static Dictionary s_converterCache = new Dictionary(256); private static ConversionData CacheConversion(Type fromType, Type toType, PSConverter converter, ConversionRank rank) { @@ -4687,9 +4686,13 @@ namespace System.Management.Automation bool debase; var conversion = FigureConversion(valueToConvert, resultType, out debase); - return conversion.Invoke(debase ? PSObject.Base(valueToConvert) : valueToConvert, - resultType, recursion, debase ? (PSObject)valueToConvert : null, - formatProvider, backupTypeTable); + return conversion.Invoke( + debase ? PSObject.Base(valueToConvert) : valueToConvert, + resultType, + recursion, + debase ? (PSObject)valueToConvert : null, + formatProvider, + backupTypeTable); } } @@ -4704,6 +4707,14 @@ namespace System.Management.Automation internal static Tuple GetInvalidCastMessages(object valueToConvert, Type resultType) { string errorId, errorMsg; + if (resultType.IsByRefLike) + { + typeConversion.WriteLine("Cannot convert to ByRef-Like types as they should be used on stack only."); + errorId = nameof(ExtendedTypeSystem.InvalidCastToByRefLikeType); + errorMsg = StringUtil.Format(ExtendedTypeSystem.InvalidCastToByRefLikeType, resultType); + return Tuple.Create(errorId, errorMsg); + } + if (PSObject.Base(valueToConvert) == null) { if (resultType.IsEnum) @@ -5443,6 +5454,12 @@ namespace System.Management.Automation toType == fromType ? ConversionRank.Identity : ConversionRank.Assignable); } + if (fromType.IsByRefLike || toType.IsByRefLike) + { + // ByRef-like types are not boxable and should be used on stack only. + return CacheConversion(fromType, toType, ConvertNoConversion, ConversionRank.None); + } + if (typeof(PSObject).IsAssignableFrom(fromType) && typeof(InternalPSObject) != fromType) { // We don't attempt converting PSObject (or derived) to anything else, diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 92d8548e9f..41084c4db2 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -391,6 +391,8 @@ namespace System.Management.Automation.Language internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeNonPublic = typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeNonPublic), staticFlags); + internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeByRefLike = + typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeByRefLike), staticFlags); internal static readonly MethodInfo PSCreateInstanceBinder_GetTargetTypeName = typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.GetTargetTypeName), staticFlags); @@ -596,7 +598,7 @@ namespace System.Management.Automation.Language return Expression.Convert(expr, type); } - if (type.ContainsGenericParameters) + if (type.ContainsGenericParameters || type.IsByRefLike) { return Expression.Call( CachedReflectionInfo.LanguagePrimitives_ThrowInvalidCastException, @@ -5504,6 +5506,22 @@ namespace System.Management.Automation.Language var propertyInfo = memberInfo[0] as PropertyInfo; if (propertyInfo != null) { + if (propertyInfo.PropertyType.IsByRefLike) + { + // ByRef-like types are not boxable and should be used only on stack. + return Expression.Throw( + Expression.New( + CachedReflectionInfo.GetValueException_ctor, + Expression.Constant(nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField)), + Expression.Constant(null, typeof(Exception)), + Expression.Constant(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField), + Expression.NewArrayInit( + typeof(object), + Expression.Constant(propertyInfo.Name), + Expression.Constant(propertyInfo.PropertyType, typeof(Type)))), + typeof(object)); + } + if (propertyInfo.CanRead) { return Expression.Property(null, propertyInfo); @@ -5512,6 +5530,8 @@ namespace System.Management.Automation.Language } else { + // Field cannot be of a ByRef-like type unless it's an instance member of a ref struct. + // So we don't need to check 'IsByRefLike' for static field access. return Expression.Field(null, (FieldInfo)memberInfo[0]); } } diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index f4ab7c73f6..ea33af9348 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -4242,6 +4242,22 @@ namespace System.Management.Automation.Language } } + // Check return type after the argument conversion, so any no-conversion error can be thrown. + if (getter.ReturnType.IsByRefLike) + { + 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.CombineRestrictions(indexes)); + } + if (getterParams.Length == 1 && getterParams[0].ParameterType == typeof(int) && CanIndexFromEndWithNegativeIndex(target)) { // PowerShell supports negative indexing for some types (specifically, types implementing IList or IList). @@ -5090,7 +5106,13 @@ namespace System.Management.Automation.Language 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) + if (adapterData.member.DeclaringType.IsGenericTypeDefinition || adapterData.propertyType.IsByRefLike) + { + // This is kinda lame - 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. @@ -5113,11 +5135,6 @@ namespace System.Management.Automation.Language expr = Expression.Field(targetExpr, (FieldInfo)adapterData.member); } } - else - { - // This is kinda lame - we really should throw an error, but V2 did the same thing (even in strict mode). - expr = ExpressionCache.NullConstant; - } } var scriptProperty = propertyInfo as PSScriptProperty; @@ -6032,14 +6049,37 @@ namespace System.Management.Automation.Language if (data.member.DeclaringType.IsGenericTypeDefinition) { - Expression innerException = Expression.New(CachedReflectionInfo.SetValueException_ctor, + 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)); + + 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) + { + 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); } @@ -6769,6 +6809,45 @@ namespace System.Management.Automation.Language List initTemps = new List(); List copyOutTemps = new List(); + ConstructorInfo constructorInfo = null; + MethodInfo methodInfo = mi as MethodInfo; + if (methodInfo != null) + { + Type returnType = methodInfo.ReturnType; + if (returnType.IsByRefLike) + { + return Expression.Throw( + Expression.New( + CachedReflectionInfo.MethodException_ctor, + 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)); + } + } + var parameters = mi.GetParameters(); var argExprs = new Expression[parameters.Length]; for (int i = 0; i < parameters.Length; ++i) @@ -6846,13 +6925,6 @@ namespace System.Management.Automation.Language } } - ConstructorInfo constructorInfo = null; - var methodInfo = mi as MethodInfo; - if (methodInfo == null) - { - constructorInfo = (ConstructorInfo)mi; - } - Expression call; if (constructorInfo != null) { @@ -7266,16 +7338,35 @@ namespace System.Management.Automation.Language 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, "MethodNotFound", ParserStrings.MethodNotFound, - Expression.Call( - CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName, - target.Expression), - Expression.Constant("new")).WriteToDebugLog(this); + + 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(); @@ -7304,7 +7395,19 @@ namespace System.Management.Automation.Language } /// - /// Check if the target type is not public + /// Check if the target type is ByRef-like. + /// + 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; + } + + /// + /// Check if the target type is not public. /// internal static bool IsTargetTypeNonPublic(object target) { @@ -7316,7 +7419,7 @@ namespace System.Management.Automation.Language } /// - /// Return the full name of the target type + /// Return the full name of the target type. /// internal static string GetTargetTypeName(object target) { diff --git a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx index 9541ee6a0d..6a697cba88 100644 --- a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx +++ b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx @@ -370,4 +370,16 @@ PS> [System.Collections.Generic.Comparer``1]::get_Default() Cannot convert value to type "{0}". Only core types are supported in this language mode. + + Cannot convert to the ByRef-like type "{0}". ByRef-like types are not supported in PowerShell. + + + Cannot get or set the property or field "{0}" of the ByRef-like type "{1}". ByRef-like types are not supported in PowerShell. + + + Cannot invoke the method "{0}" of the ByRef-like return type "{1}". ByRef-like types are not supported in PowerShell. + + + Cannot create an instance of the ByRef-like type "{0}". ByRef-like types are not supported in PowerShell. + diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index c63a63d1a4..ccf40602e2 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -291,7 +291,10 @@ Possible matches are Cannot index into a null array. - Unable to index into an object of type {0}. + Unable to index into an object of type "{0}". + + + Unable to index into an object of type "{0}" with the ByRef-like return type "{1}". ByRef-like types are not supported in PowerShell. Array assignment to [{0}] failed: {1}. diff --git a/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 b/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 index f5e72690ad..d717a7bfb4 100644 --- a/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 +++ b/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + Describe "DotNetAPI" -Tags "CI" { $posh_E = 2.718281828459045 $posh_pi = 3.14159265358979 @@ -24,4 +25,8 @@ Describe "DotNetAPI" -Tags "CI" { $guidVal | Should -BeOfType Guid } + + It "Should access types in System.Console" { + [System.Console]::TreatControlCAsInput | Should -BeFalse + } } diff --git a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 index 63884843a9..cf7214ab51 100644 --- a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 +++ b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 @@ -1,7 +1,186 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -Describe ".NET class interoperability" -Tags "CI" { - It "Should access types in System.Console" { - [System.Console]::TreatControlCAsInput | Should -BeFalse + +Describe "Handle ByRef-like types gracefully" -Tags "CI" { + + BeforeAll { + $code = @' +using System; +namespace DotNetInterop +{ + public class Test + { + public Span this[int i] + { + get { return default(Span); } + set { DoNothing(value); } + } + + public static Span Space + { + get { return default(Span); } + set { DoNothing(value); } + } + + public Span Room + { + get { return default(Span); } + set { DoNothing(value); } + } + + private static void DoNothing(Span param) + { + } + + public string PrintMySpan(string str, Span mySpan = default) + { + return str; + } + + public Span GetSpan(int[] array) + { + return array.AsSpan(); + } + } + + public ref struct MyByRefLikeType + { + public MyByRefLikeType(int i) { } + public static int Index; + } +} +'@ + if (-not ("DotNetInterop.Test" -as [type])) + { + Add-Type -TypeDefinition $code -IgnoreWarnings + } + + $testObj = [DotNetInterop.Test]::new() + } + + It "New-Object should fail gracefully when used for a ByRef-like type" { + { New-Object -TypeName 'System.Span[string]' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand" + { New-Object -TypeName 'DotNetInterop.MyByRefLikeType' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand" + } + + It "The 'new' method call should fail gracefully when used on a ByRef-like type" { + { [System.Span[string]]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType" + { [DotNetInterop.MyByRefLikeType]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType" + } + + It "Calling constructor of a ByRef-like type via dotnet adapter should fail gracefully - " -TestCases @( + @{ Number = 1; Script = { [System.Span[string]]::new.Invoke("abc") } } + @{ Number = 2; Script = { [DotNetInterop.MyByRefLikeType]::new.Invoke(2) } } + ) { + param($Script) + $expectedError = $null + try { + & $Script + } catch { + $expectedError = $_ + } + + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotInstantiateBoxedByRefLikeType" + } + + It "Cast to a ByRef-like type should fail gracefully" { + { [System.Span[int]] ([int[]]1,2,3) } | Should -Throw -ErrorId "InvalidCastToByRefLikeType" + { [DotNetInterop.MyByRefLikeType] "text" } | Should -Throw -ErrorId "InvalidCastToByRefLikeType" + } + + It "LanguagePrimitives.ConvertTo should fail gracefully for a ByRef-like type ''" -TestCases @( + @{ Name = "Span"; Type = [System.Span[int]] } + @{ Name = "MyByRefLikeType"; Type = [DotNetInterop.MyByRefLikeType] } + ) { + param($Type) + $expectedError = $null + try { + [System.Management.Automation.LanguagePrimitives]::ConvertTo(([int[]]1,2,3), $Type) + } catch { + $expectedError = $_ + } + + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "InvalidCastToByRefLikeType" + } + + It "Getting value of a ByRef-like type instance property should not throw and should return null, even in strict mode - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { [System.Text.Encoding]::ASCII.Preamble } } + @{ Mechanism = "Dotnet-Adapter"; Script = { [System.Text.Encoding]::ASCII.PSObject.Properties["Preamble"].Value } } + ) { + param($Script) + + try { + Set-StrictMode -Version latest + & $Script | Should -Be $null + } finally { + Set-StrictMode -Off + } + } + + It "Setting value of a ByRef-like type instance property should fail gracefully - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { $testObj.Room = [int[]](1,2,3) } } + @{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.PSObject.Properties["Room"].Value = [int[]](1,2,3) } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField" + } + + It " value of a ByRef-like type static property should fail gracefully" -TestCases @( + @{ Action = "Getting"; Script = { [DotNetInterop.Test]::Space } } + @{ Action = "Setting"; Script = { [DotNetInterop.Test]::Space = "blah" } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField" + } + + It "Invoke a method with optional ByRef-like parameter could work" { + $testObj.PrintMySpan("Hello") | Should -BeExactly "Hello" + } + + It "Invoke a method with ByRef-like parameter should fail gracefully - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { $testObj.PrintMySpan("Hello", 1) } } + @{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.psobject.Methods["PrintMySpan"].Invoke("Hello", 1) } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument" + } + + It "Invoke a method with ByRef-like return type should fail gracefully - Compiler/Binder" { + { $testObj.GetSpan([int[]]@(1,2,3)) } | Should -Throw -ErrorId "CannotCallMethodWithByRefLikeReturnType" + } + + It "Invoke a method with ByRef-like return type should fail gracefully - Dotnet-Adapter" { + $expectedError = $null + try { + $testObj.psobject.Methods["GetSpan"].Invoke([int[]]@(1,2,3)) + } catch { + $expectedError = $_ + } + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotCallMethodWithByRefLikeReturnType" + } + + It "Access static property of a ByRef-like type" { + [DotNetInterop.MyByRefLikeType]::Index = 10 + [DotNetInterop.MyByRefLikeType]::Index | Should -Be 10 + } + + It "Get access of an indexer that returns ByRef-like type should return null in no-strict mode" { + $testObj[1] | Should -Be $null + } + + It "Get access of an indexer that returns ByRef-like type should fail gracefully in strict mode" { + try { + Set-StrictMode -Version latest + { $testObj[1] } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType" + } finally { + Set-StrictMode -Off + } + } + + It "Set access of an indexer that accepts ByRef-like type should fail gracefully" { + { $testObj[1] = 1 } | Should -Throw -ErrorId "InvalidCastToByRefLikeType" } }