8000 Make inherited protected internal instance members accessible in Powe… · PowerShell/PowerShell@eb1cc6d · GitHub
[go: up one dir, main page]

Skip to content

Commit eb1cc6d

Browse files
authored
Make inherited protected internal instance members accessible in PowerShell class scope (#25245)
1 parent fcc1833 commit eb1cc6d

File tree

3 files changed

+217
-9
lines changed

3 files changed

+217
-9
lines changed

src/System.Management.Automation/engine/CoreAdapter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2835,7 +2835,7 @@ internal PropertyCacheEntry(PropertyInfo property)
28352835

28362836
// Get the public or protected getter
28372837
MethodInfo propertyGetter = property.GetGetMethod(true);
2838-
if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily))
2838+
if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly))
28392839
{
28402840
this.isStatic = propertyGetter.IsStatic;
28412841
// Delegate is initialized later to avoid jit if it's not called
@@ -2847,7 +2847,7 @@ internal PropertyCacheEntry(PropertyInfo property)
28472847

28482848
// Get the public or protected setter
28492849
MethodInfo propertySetter = property.GetSetMethod(true);
2850-
if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily))
2850+
if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly))
28512851
{
28522852
this.isStatic = propertySetter.IsStatic;
28532853
}

src/System.Management.Automation/engine/runtime/Binding/Binders.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -5298,7 +5298,8 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy
52985298
var propertyAccessor = adapterData.member as PropertyInfo;
52995299
if (propertyAccessor != null)
53005300
{
5301-
if (propertyAccessor.GetMethod.IsFamily &&
5301+
var propertyGetter = propertyAccessor.GetMethod;
5302+
if ((propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly) &&
53025303
(_classScope == null || !_classScope.IsSubclassOf(propertyAccessor.DeclaringType)))
53035304
{
53045305
return GenerateGetPropertyException(restrictions).WriteToDebugLog(this);
@@ -5758,8 +5759,8 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
57585759
var getMethod = propertyInfo.GetGetMethod(nonPublic: true);
57595760
var setMethod = propertyInfo.GetSetMethod(nonPublic: true);
57605761

5761-
if ((getMethod == null || getMethod.IsFamily || getMethod.IsPublic) &&
5762-
(setMethod == null || setMethod.IsFamily || setMethod.IsPublic))
5762+
if ((getMethod == null || getMethod.IsPublic || getMethod.IsFamily || getMethod.IsFamilyOrAssembly) &&
5763+
(setMethod == null || setMethod.IsPublic || setMethod.IsFamily || setMethod.IsFamilyOrAssembly))
57635764
{
57645765
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo));
57655766
}
@@ -5769,15 +5770,15 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
57695770
var fieldInfo = member as FieldInfo;
57705771
if (fieldInfo != null)
57715772
{
5772-
if (fieldInfo.IsFamily)
5773+
if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly)
57735774
{
57745775
memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo));
57755776
}
57765777
}
57775778
else
57785779
{
57795780
var methodInfo = member as MethodInfo;
5780-
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily))
5781+
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly))
57815782
{
57825783
candidateMethods ??= new List<MethodBase>();
57835784

@@ -6292,7 +6293,8 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
62926293
var targetExpr = _static ? null : PSGetMemberBinder.GetTargetExpr(target, data.member.DeclaringType);
62936294
if (propertyInfo != null)
62946295
{
6295-
if (propertyInfo.SetMethod.IsFamily &&
6296+
var propertySetter = propertyInfo.SetMethod;
6297+
if ((propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly) &&
62966298
(_classScope == null || !_classScope.IsSubclassOf(propertyInfo.DeclaringType)))
62976299
{
62986300
return GeneratePropertyAssignmentException(restrictions).WriteToDebugLog(this);
@@ -7902,7 +7904,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
79027904
? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType())
79037905
: target.PSGetTypeRestriction();
79047906
restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
7905-
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily).ToArray());
7907+
var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily || c.IsFamilyOrAssembly).ToArray());
79067908
return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.BaseCtor,
79077909
target, args, restrictions, newConstructors, typeof(MethodException));
79087910
}

test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1

+206
Original file line numberDiff line numberDiff line change
@@ -715,3 +715,209 @@ Describe 'Base type has abstract properties' -Tags "CI" {
715715
$failure.Exception.Message | Should -BeLike "*'get_Exists'*"
716716
}
717717
}
718+
719+
Describe 'Classes inheritance with protected and protected internal members in base class' -Tags 'CI' {
720+
721+
BeforeAll {
722+
Set-StrictMode -Version 3
723+
$c1DefinitionProtectedInternal = @'
724+
public class C1ProtectedInternal
725+
{
726+
protected internal string InstanceField = "C1_InstanceField";
727+
protected internal string InstanceProperty { get; set; } = "C1_InstanceProperty";
728+
protected internal string InstanceMethod() { return "C1_InstanceMethod"; }
729+
730+
protected internal virtual string VirtualProperty1 { get; set; } = "C1_VirtualProperty1";
731+
protected internal virtual string VirtualProperty2 { get; set; } = "C1_VirtualProperty2";
732+
protected internal virtual string VirtualMethod1() { return "C1_VirtualMethod1"; }
733+
protected internal virtual string VirtualMethod2() { return "C1_VirtualMethod2"; }
734+
735+
public string CtorUsed { get; set; }
736+
public C1ProtectedInternal() { CtorUsed = "default ctor"; }
737+
protected internal C1ProtectedInternal(string p1) { CtorUsed = "C1_ctor_1args:" + p1; }
738+
}
739+
'@
740+
$c2DefinitionProtectedInternal = @'
741+
class C2ProtectedInternal : C1ProtectedInternal {
742+
C2ProtectedInternal() : base() { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
743+
C2ProtectedInternal([string]$p1) : base($p1) { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
744+
745+
[string]GetInstanceField() { return $this.InstanceField }
746+
[string]SetInstanceField([string]$value) { $this.InstanceField = $value; return $this.InstanceField }
747+
[string]GetInstanceProperty() { return $this.InstanceProperty }
748+
[string]SetInstanceProperty([string]$value) { $this.InstanceProperty = $value; return $this.InstanceProperty }
749+
[string]CallInstanceMethod() { return $this.InstanceMethod() }
750+
751+
[string]GetVirtualProperty1() { return $this.VirtualProperty1 }
752+
[string]SetVirtualProperty1([string]$value) { $this.VirtualProperty1 = $value; return $this.VirtualProperty1 }
753+
[string]CallVirtualMethod1() { return $this.VirtualMethod1() }
754+
755+
[string]$VirtualProperty2
756+
[string]VirtualMethod2() { return 'C2_VirtualMethod2' }
757+
# Note: Overriding a virtual property in a derived PowerShell class prevents access to the
758+
# base property via simple typecast ([base]$this).VirtualProperty2.
759+
[string]GetVirtualProperty2() { return $this.VirtualProperty2 }
760+
[string]SetVirtualProperty2([string]$value) { $this.VirtualProperty2 = $value; return $this.VirtualProperty2 }
761+
[string]CallVirtualMethod2Base() { return ([C1ProtectedInternal]$this).VirtualMethod2() }
762+
[string]CallVirtualMethod2Derived() { return $this.VirtualMethod2() }
763+
764+
[string]GetInstanceMemberDynamic([string]$name) { return $this.$name }
765+
[string]SetInstanceMemberDynamic([string]$name, [string]$value) { $this.$name = $value; return $this.$name }
766+
[string]CallInstanceMemberDynamic([string]$name) { return $this.$name() }
767+
}
768+
769+
[C2ProtectedInternal]
770+
'@
771+
772+
Add-Type -TypeDefinition $c1DefinitionProtectedInternal
773+
Add-Type -TypeDefinition (($c1DefinitionProtectedInternal -creplace 'C1ProtectedInternal', 'C1Protected') -creplace 'protected internal', 'protected')
774+
775+
$testCases = @(
776+
@{ accessType = 'protected'; derivedType = Invoke-Expression ($c2DefinitionProtectedInternal -creplace 'ProtectedInternal', 'Protected') }
777+
@{ accessType = 'protected internal'; derivedType = Invoke-Expression $c2DefinitionProtectedInternal }
778+
)
779+
}
780+
781+
AfterAll {
782+
Set-StrictMode -Off
783+
}
784+
785+
Context 'Derived class can access instance base class members' {
786+
787+
It 'can call protected internal .NET method Object.MemberwiseClone()' {
788+
class CNetMethod {
789+
[string]$Foo
790+
[object]CloneIt() { return $this.MemberwiseClone() }
791+
}
792+
$c1 = [CNetMethod]::new()
793+
$c1.Foo = 'bar'
794+
$c2 = $c1.CloneIt()
795+
$c2.Foo | Should -Be 'bar'
796+
}
797+
798+
It 'can call <accessType> base ctor' -TestCases $testCases {
799+
param($derivedType)
800+
$derivedType::new('foo').CtorUsed | Should -Be 'C1_ctor_1args:foo'
801+
}
802+
803+
It 'can access <accessType> base field' -TestCases $testCases {
804+
param($derivedType)
805+
$c2 = $derivedType::new()
806+
$c2.GetInstanceField() | Should -Be 'C1_InstanceField'
807+
$c2.SetInstanceField('foo_InstanceField') | Should -Be 'foo_InstanceField'
808+
}
809+
810+
It 'can access <accessType> base property' -TestCases $testCases {
811+
param($derivedType)
812+
$c2 = $derivedType::new()
813+
$c2.GetInstanceProperty() | Should -Be 'C1_InstanceProperty'
814+
$c2.SetInstanceProperty('foo_InstanceProperty') | Should -Be 'foo_InstanceProperty'
815+
}
816+
817+
It 'can call <accessType> base method' -TestCases $testCases {
818+
param($derivedType)
819+
$derivedType::new().CallInstanceMethod() | Should -Be 'C1_InstanceMethod'
820+
}
821+
822+
It 'can access <accessType> virtual base property' -TestCases $testCases {
823+
param($derivedType)
824+
$c2 = $derivedType::new()
825+
$c2.GetVirtualProperty1() | Should -Be 'C1_VirtualProperty1'
826+
$c2.SetVirtualProperty1('foo_VirtualProperty1') | Should -Be 'foo_VirtualProperty1'
827+
}
828+
829+
It 'can call <accessType> virtual base method' -TestCases $testCases {
830+
param($derivedType)
831+
$derivedType::new().CallVirtualMethod1() | Should -Be 'C1_VirtualMethod1'
832+
}
833+
}
834+
835+
Context 'Derived class can override virtual base class members' {
836+
837+
It 'can override <accessType> virtual base property' -TestCases $testCases {
838+
param($derivedType)
839+
$c2 = $derivedType::new()
840+
$c2.GetVirtualProperty2() | Should -Be 'C2_VirtualProperty2'
841+
$c2.SetVirtualProperty2('foo_VirtualProperty2') | Should -Be 'foo_VirtualProperty2'
842+
}
843+
844+
It 'can override <accessType> virtual base method' -TestCases $testCases {
845+
param($derivedType)
846+
$c2 = $derivedType::new()
847+
$c2.CallVirtualMethod2Base() | Should -Be 'C1_VirtualMethod2'
848+
$c2.CallVirtualMethod2Derived() | Should -Be 'C2_VirtualMethod2'
849+
}
850+
}
851+
852+
Context 'Derived class can access instance base class members dynamically' {
853+
854+
It 'can access <accessType> base fields and properties' -TestCases $testCases {
855+
param($derivedType)
856+
$c2 = $derivedType::new()
857+
$c2.GetInstanceMemberDynamic('InstanceField') | Should -Be 'C1_InstanceField'
858+
$c2.GetInstanceMemberDynamic('InstanceProperty') | Should -Be 'C1_InstanceProperty'
859+
$c2.GetInstanceMemberDynamic('VirtualProperty1') | Should -Be 'C1_VirtualProperty1'
860+
$c2.SetInstanceMemberDynamic('InstanceField', 'foo1') | Should -Be 'foo1'
861+
$c2.SetInstanceMemberDynamic('InstanceProperty', 'foo2') | Should -Be 'foo2'
862+
$c2.SetInstanceMemberDynamic('VirtualProperty1', 'foo3') | Should -Be 'foo3'
863+
}
864+
865+
It 'can call <accessType> base methods' -TestCases $testCases {
866+
param($derivedType)
867+
$c2 = $derivedType::new()
868+
$c2.CallInstanceMemberDynamic('InstanceMethod') | Should -Be 'C1_InstanceMethod'
869+
$c2.CallInstanceMemberDynamic('VirtualMethod1') | Should -Be 'C1_VirtualMethod1'
870+
}
871+
}
872+
873+
Context 'Base class members are not accessible outside class scope' {
874+
875+
BeforeAll {
876+
$instanceTest = {
877+
$c2 = $derivedType::new()
878+
{ $null = $c2.InstanceField } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
879+
{ $null = $c2.InstanceProperty } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
880+
{ $null = $c2.VirtualProperty1 } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
881+
{ $c2.InstanceField = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
882+
{ $c2.InstanceProperty = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
883+
{ $c2.VirtualProperty1 = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
884+
{ $derivedType::new().InstanceMethod() } | Should -Throw -ErrorId 'MethodNotFound'
885+
{ $derivedType::new().VirtualMethod1() } | Should -Throw -ErrorId 'MethodNotFound'
886+
foreach ($name in @('InstanceField', 'InstanceProperty', 'VirtualProperty1')) {
887+
{ $null = $c2.$name } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
888+
{ $c2.$name = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
889+
}
890+
foreach ($name in @('InstanceMethod', 'VirtualMethod1')) {
891+
{ $c2.$name() } | Should -Throw -ErrorId 'MethodNotFound'
892+
}
893+
}
894+
$c3UnrelatedType = Invoke-Expression @"
895+
class C3Unrelated {
896+
[void]RunInstanceTest([type]`$derivedType) { $instanceTest }
897+
}
898+
[C3Unrelated]
899+
"@
900+
$negativeTestCases = $testCases.ForEach({
901+
$item = $_.Clone()
902+
$item['scopeType'] = 'null scope'
903+
$item['classScope'] = $null
904+
$item
905+
$item = $_.Clone()
906+
$item['scopeType'] = 'unrelated class scope'
907+
$item['classScope'] = $c3UnrelatedType
908+
$item
909+
})
910+
}
911+
912+
It 'cannot access <accessType> instance base members in <scopeType>' -TestCases $negativeTestCases {
913+
param($derivedType, $classScope)
914+
if ($null -eq $classScope) {
915+
$instanceTest.Invoke()
916+
}
917+
else {
918+
$c3 = $classScope::new()
919+
$c3.RunInstanceTest($derivedType)
920+
}
921+
}
922+
}
923+
}

0 commit comments

Comments
 (0)
0