Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/System.Management.Automation/engine/CoreAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2835,7 +2835,7 @@ internal PropertyCacheEntry(PropertyInfo property)

// 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
Expand All @@ -2847,7 +2847,7 @@ internal PropertyCacheEntry(PropertyInfo property)

// 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5297,7 +5297,8 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy
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);
Expand Down Expand Up @@ -5757,8 +5758,8 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
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));
}
Expand All @@ -5768,15 +5769,15 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
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));
}
}
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>();

Expand Down Expand Up @@ -6291,7 +6292,8 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
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);
Expand Down Expand Up @@ -7891,7 +7893,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
? 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Loading