mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Make inherited protected internal instance members accessible in PowerShell class scope (#25245)
This commit is contained in:
@@ -2835,7 +2835,7 @@ namespace System.Management.Automation
|
||||
|
||||
// Get the public or protected getter
|
||||
MethodInfo propertyGetter = property.GetGetMethod(true);
|
||||
if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily))
|
||||
if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly))
|
||||
{
|
||||
this.isStatic = propertyGetter.IsStatic;
|
||||
// Delegate is initialized later to avoid jit if it's not called
|
||||
@@ -2847,7 +2847,7 @@ namespace System.Management.Automation
|
||||
|
||||
// Get the public or protected setter
|
||||
MethodInfo propertySetter = property.GetSetMethod(true);
|
||||
if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily))
|
||||
if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly))
|
||||
{
|
||||
this.isStatic = propertySetter.IsStatic;
|
||||
}
|
||||
|
||||
@@ -5298,7 +5298,8 @@ namespace System.Management.Automation.Language
|
||||
var propertyAccessor = adapterData.member as PropertyInfo;
|
||||
if (propertyAccessor != null)
|
||||
{
|
||||
if (propertyAccessor.GetMethod.IsFamily &&
|
||||
var propertyGetter = propertyAccessor.GetMethod;
|
||||
if ((propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly) &&
|
||||
(_classScope == null || !_classScope.IsSubclassOf(propertyAccessor.DeclaringType)))
|
||||
{
|
||||
return GenerateGetPropertyException(restrictions).WriteToDebugLog(this);
|
||||
@@ -5758,8 +5759,8 @@ namespace System.Management.Automation.Language
|
||||
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))
|
||||
if ((getMethod == null || getMethod.IsPublic || getMethod.IsFamily || getMethod.IsFamilyOrAssembly) &&
|
||||
(setMethod == null || setMethod.IsPublic || setMethod.IsFamily || setMethod.IsFamilyOrAssembly))
|
||||
{
|
||||
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo));
|
||||
}
|
||||
@@ -5769,7 +5770,7 @@ namespace System.Management.Automation.Language
|
||||
var fieldInfo = member as FieldInfo;
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
if (fieldInfo.IsFamily)
|
||||
if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly)
|
||||
{
|
||||
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo));
|
||||
}
|
||||
@@ -5777,7 +5778,7 @@ namespace System.Management.Automation.Language
|
||||
else
|
||||
{
|
||||
var methodInfo = member as MethodInfo;
|
||||
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily))
|
||||
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly))
|
||||
{
|
||||
candidateMethods ??= new List<MethodBase>();
|
||||
|
||||
@@ -6292,7 +6293,8 @@ namespace System.Management.Automation.Language
|
||||
var targetExpr = _static ? null : PSGetMemberBinder.GetTargetExpr(target, data.member.DeclaringType);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
if (propertyInfo.SetMethod.IsFamily &&
|
||||
var propertySetter = propertyInfo.SetMethod;
|
||||
if ((propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly) &&
|
||||
(_classScope == null || !_classScope.IsSubclassOf(propertyInfo.DeclaringType)))
|
||||
{
|
||||
return GeneratePropertyAssignmentException(restrictions).WriteToDebugLog(this);
|
||||
@@ -7902,7 +7904,7 @@ namespace System.Management.Automation.Language
|
||||
? 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());
|
||||
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily || c.IsFamilyOrAssembly).ToArray());
|
||||
return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.BaseCtor,
|
||||
target, args, restrictions, newConstructors, typeof(MethodException));
|
||||
}
|
||||
|
||||
@@ -715,3 +715,209 @@ Describe 'Base type has abstract properties' -Tags "CI" {
|
||||
$failure.Exception.Message | Should -BeLike "*'get_Exists'*"
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Classes inheritance with protected and protected internal members in base class' -Tags 'CI' {
|
||||
|
||||
BeforeAll {
|
||||
Set-StrictMode -Version 3
|
||||
$c1DefinitionProtectedInternal = @'
|
||||
public class C1ProtectedInternal
|
||||
{
|
||||
protected internal string InstanceField = "C1_InstanceField";
|
||||
protected internal string InstanceProperty { get; set; } = "C1_InstanceProperty";
|
||||
protected internal string InstanceMethod() { return "C1_InstanceMethod"; }
|
||||
|
||||
protected internal virtual string VirtualProperty1 { get; set; } = "C1_VirtualProperty1";
|
||||
protected internal virtual string VirtualProperty2 { get; set; } = "C1_VirtualProperty2";
|
||||
protected internal virtual string VirtualMethod1() { return "C1_VirtualMethod1"; }
|
||||
protected internal virtual string VirtualMethod2() { return "C1_VirtualMethod2"; }
|
||||
|
||||
public string CtorUsed { get; set; }
|
||||
public C1ProtectedInternal() { CtorUsed = "default ctor"; }
|
||||
protected internal C1ProtectedInternal(string p1) { CtorUsed = "C1_ctor_1args:" + p1; }
|
||||
}
|
||||
'@
|
||||
$c2DefinitionProtectedInternal = @'
|
||||
class C2ProtectedInternal : C1ProtectedInternal {
|
||||
C2ProtectedInternal() : base() { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
|
||||
C2ProtectedInternal([string]$p1) : base($p1) { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
|
||||
|
||||
[string]GetInstanceField() { return $this.InstanceField }
|
||||
[string]SetInstanceField([string]$value) { $this.InstanceField = $value; return $this.InstanceField }
|
||||
[string]GetInstanceProperty() { return $this.InstanceProperty }
|
||||
[string]SetInstanceProperty([string]$value) { $this.InstanceProperty = $value; return $this.InstanceProperty }
|
||||
[string]CallInstanceMethod() { return $this.InstanceMethod() }
|
||||
|
||||
[string]GetVirtualProperty1() { return $this.VirtualProperty1 }
|
||||
[string]SetVirtualProperty1([string]$value) { $this.VirtualProperty1 = $value; return $this.VirtualProperty1 }
|
||||
[string]CallVirtualMethod1() { return $this.VirtualMethod1() }
|
||||
|
||||
[string]$VirtualProperty2
|
||||
[string]VirtualMethod2() { return 'C2_VirtualMethod2' }
|
||||
# Note: Overriding a virtual property in a derived PowerShell class prevents access to the
|
||||
# base property via simple typecast ([base]$this).VirtualProperty2.
|
||||
[string]GetVirtualProperty2() { return $this.VirtualProperty2 }
|
||||
[string]SetVirtualProperty2([string]$value) { $this.VirtualProperty2 = $value; return $this.VirtualProperty2 }
|
||||
[string]CallVirtualMethod2Base() { return ([C1ProtectedInternal]$this).VirtualMethod2() }
|
||||
[string]CallVirtualMethod2Derived() { return $this.VirtualMethod2() }
|
||||
|
||||
[string]GetInstanceMemberDynamic([string]$name) { return $this.$name }
|
||||
[string]SetInstanceMemberDynamic([string]$name, [string]$value) { $this.$name = $value; return $this.$name }
|
||||
[string]CallInstanceMemberDynamic([string]$name) { return $this.$name() }
|
||||
}
|
||||
|
||||
[C2ProtectedInternal]
|
||||
'@
|
||||
|
||||
Add-Type -TypeDefinition $c1DefinitionProtectedInternal
|
||||
Add-Type -TypeDefinition (($c1DefinitionProtectedInternal -creplace 'C1ProtectedInternal', 'C1Protected') -creplace 'protected internal', 'protected')
|
||||
|
||||
$testCases = @(
|
||||
@{ accessType = 'protected'; derivedType = Invoke-Expression ($c2DefinitionProtectedInternal -creplace 'ProtectedInternal', 'Protected') }
|
||||
@{ accessType = 'protected internal'; derivedType = Invoke-Expression $c2DefinitionProtectedInternal }
|
||||
)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Set-StrictMode -Off
|
||||
}
|
||||
|
||||
Context 'Derived class can access instance base class members' {
|
||||
|
||||
It 'can call protected internal .NET method Object.MemberwiseClone()' {
|
||||
class CNetMethod {
|
||||
[string]$Foo
|
||||
[object]CloneIt() { return $this.MemberwiseClone() }
|
||||
}
|
||||
$c1 = [CNetMethod]::new()
|
||||
$c1.Foo = 'bar'
|
||||
$c2 = $c1.CloneIt()
|
||||
$c2.Foo | Should -Be 'bar'
|
||||
}
|
||||
|
||||
It 'can call <accessType> base ctor' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$derivedType::new('foo').CtorUsed | Should -Be 'C1_ctor_1args:foo'
|
||||
}
|
||||
|
||||
It 'can access <accessType> base field' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.GetInstanceField() | Should -Be 'C1_InstanceField'
|
||||
$c2.SetInstanceField('foo_InstanceField') | Should -Be 'foo_InstanceField'
|
||||
}
|
||||
|
||||
It 'can access <accessType> base property' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.GetInstanceProperty() | Should -Be 'C1_InstanceProperty'
|
||||
$c2.SetInstanceProperty('foo_InstanceProperty') | Should -Be 'foo_InstanceProperty'
|
||||
}
|
||||
|
||||
It 'can call <accessType> base method' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$derivedType::new().CallInstanceMethod() | Should -Be 'C1_InstanceMethod'
|
||||
}
|
||||
|
||||
It 'can access <accessType> virtual base property' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.GetVirtualProperty1() | Should -Be 'C1_VirtualProperty1'
|
||||
$c2.SetVirtualProperty1('foo_VirtualProperty1') | Should -Be 'foo_VirtualProperty1'
|
||||
}
|
||||
|
||||
It 'can call <accessType> virtual base method' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$derivedType::new().CallVirtualMethod1() | Should -Be 'C1_VirtualMethod1'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Derived class can override virtual base class members' {
|
||||
|
||||
It 'can override <accessType> virtual base property' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.GetVirtualProperty2() | Should -Be 'C2_VirtualProperty2'
|
||||
$c2.SetVirtualProperty2('foo_VirtualProperty2') | Should -Be 'foo_VirtualProperty2'
|
||||
}
|
||||
|
||||
It 'can override <accessType> virtual base method' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.CallVirtualMethod2Base() | Should -Be 'C1_VirtualMethod2'
|
||||
$c2.CallVirtualMethod2Derived() | Should -Be 'C2_VirtualMethod2'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Derived class can access instance base class members dynamically' {
|
||||
|
||||
It 'can access <accessType> base fields and properties' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.GetInstanceMemberDynamic('InstanceField') | Should -Be 'C1_InstanceField'
|
||||
$c2.GetInstanceMemberDynamic('InstanceProperty') | Should -Be 'C1_InstanceProperty'
|
||||
$c2.GetInstanceMemberDynamic('VirtualProperty1') | Should -Be 'C1_VirtualProperty1'
|
||||
$c2.SetInstanceMemberDynamic('InstanceField', 'foo1') | Should -Be 'foo1'
|
||||
$c2.SetInstanceMemberDynamic('InstanceProperty', 'foo2') | Should -Be 'foo2'
|
||||
$c2.SetInstanceMemberDynamic('VirtualProperty1', 'foo3') | Should -Be 'foo3'
|
||||
}
|
||||
|
||||
It 'can call <accessType> base methods' -TestCases $testCases {
|
||||
param($derivedType)
|
||||
$c2 = $derivedType::new()
|
||||
$c2.CallInstanceMemberDynamic('InstanceMethod') | Should -Be 'C1_InstanceMethod'
|
||||
$c2.CallInstanceMemberDynamic('VirtualMethod1') | Should -Be 'C1_VirtualMethod1'
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Base class members are not accessible outside class scope' {
|
||||
|
||||
BeforeAll {
|
||||
$instanceTest = {
|
||||
$c2 = $derivedType::new()
|
||||
{ $null = $c2.InstanceField } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
|
||||
{ $null = $c2.InstanceProperty } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
|
||||
{ $null = $c2.VirtualProperty1 } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
|
||||
{ $c2.InstanceField = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
|
||||
{ $c2.InstanceProperty = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
|
||||
{ $c2.VirtualProperty1 = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
|
||||
{ $derivedType::new().InstanceMethod() } | Should -Throw -ErrorId 'MethodNotFound'
|
||||
{ $derivedType::new().VirtualMethod1() } | Should -Throw -ErrorId 'MethodNotFound'
|
||||
foreach ($name in @('InstanceField', 'InstanceProperty', 'VirtualProperty1')) {
|
||||
{ $null = $c2.$name } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
|
||||
{ $c2.$name = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
|
||||
}
|
||||
foreach ($name in @('InstanceMethod', 'VirtualMethod1')) {
|
||||
{ $c2.$name() } | Should -Throw -ErrorId 'MethodNotFound'
|
||||
}
|
||||
}
|
||||
$c3UnrelatedType = Invoke-Expression @"
|
||||
class C3Unrelated {
|
||||
[void]RunInstanceTest([type]`$derivedType) { $instanceTest }
|
||||
}
|
||||
[C3Unrelated]
|
||||
"@
|
||||
$negativeTestCases = $testCases.ForEach({
|
||||
$item = $_.Clone()
|
||||
$item['scopeType'] = 'null scope'
|
||||
$item['classScope'] = $null
|
||||
$item
|
||||
$item = $_.Clone()
|
||||
$item['scopeType'] = 'unrelated class scope'
|
||||
$item['classScope'] = $c3UnrelatedType
|
||||
$item
|
||||
})
|
||||
}
|
||||
|
||||
It 'cannot access <accessType> instance base members in <scopeType>' -TestCases $negativeTestCases {
|
||||
param($derivedType, $classScope)
|
||||
if ($null -eq $classScope) {
|
||||
$instanceTest.Invoke()
|
||||
}
|
||||
else {
|
||||
$c3 = $classScope::new()
|
||||
$c3.RunInstanceTest($derivedType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user