Skip to content
Open
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
145 changes: 81 additions & 64 deletions src/System.Management.Automation/engine/runtime/Binding/Binders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5632,6 +5632,66 @@ private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject targe
return result;
}

private void GetPSMemberInfoFromClassScopeMembers(DynamicMetaObject target, MemberInfo[] members, DotNetAdapter dotNetAdapter, ref PSMemberInfo memberInfo)
{
List<MethodBase> candidateMethods = null;
foreach (var member in members)
{
if (this.Name.Equals(member.Name, StringComparison.OrdinalIgnoreCase))
{
if (member is PropertyInfo propertyInfo)
{
var getMethod = propertyInfo.GetGetMethod(nonPublic: true);
var setMethod = propertyInfo.GetSetMethod(nonPublic: true);

if ((getMethod == null || getMethod.IsPublic || getMethod.IsFamily || getMethod.IsFamilyOrAssembly) &&
(setMethod == null || setMethod.IsPublic || setMethod.IsFamily || setMethod.IsFamilyOrAssembly))
{
memberInfo = new PSProperty(this.Name, dotNetAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo));
}
}
else if (member is FieldInfo fieldInfo)
{
if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly)
{
memberInfo = new PSProperty(this.Name, dotNetAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo));
}
}
else if (member is MethodInfo methodInfo)
{
if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly))
{
candidateMethods ??= new List<MethodBase>();

candidateMethods.Add(methodInfo);
}
}
}
}

if (candidateMethods != null && candidateMethods.Count > 0)
{
var psMethodInfo = memberInfo as PSMethod;
if (psMethodInfo != null)
{
var cacheEntry = (DotNetAdapter.MethodCacheEntry)psMethodInfo.adapterData;
candidateMethods.AddRange(cacheEntry.methodInformationStructures.Select(static e => e.method));
memberInfo = null;
}

if (memberInfo != null)
{
// Ambiguous, it'd be better to report an error other than "can't find member", but I'm lazy.
memberInfo = null;
}
else
{
DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods);
memberInfo = PSMethod.Create(this.Name, dotNetAdapter, null, method);
}
}
}

internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
out BindingRestrictions restrictions,
out bool canOptimize,
Expand All @@ -5644,6 +5704,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
bool hasTypeTableMember;
bool hasInstanceMember;
BindingRestrictions versionRestriction;
PSMemberInfo memberInfo = null;
lock (this)
{
versionRestriction = BinderUtils.GetVersionCheck(this, _version);
Expand All @@ -5657,15 +5718,28 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
restrictions = restrictions.Merge(versionRestriction);
canOptimize = true;

return PSObject.GetStaticCLRMember(target.Value, Name);
memberInfo = PSObject.GetStaticCLRMember(target.Value, Name);
if (memberInfo == null && _classScope != null)
{
var obj = PSObject.Base(target.Value);
var objType = obj as Type ?? obj?.GetType();
if (objType != null && (objType == _classScope || objType.IsSubclassOf(_classScope)))
{
GetPSMemberInfoFromClassScopeMembers(
target,
_classScope.GetMembers(BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic),
PSObject.DotNetStaticAdapter,
ref memberInfo);
}
}
return memberInfo;
}

canOptimize = false;

Diagnostics.Assert(!TryGetInstanceMember(target.Value, Name, out _),
"shouldn't get here if there is an instance member");

PSMemberInfo memberInfo = null;
ConsolidatedString typenames = null;
var context = LocalPipeline.GetExecutionContextFromTLS();
var typeTable = context?.TypeTable;
Expand Down Expand Up @@ -5748,68 +5822,11 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,

if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.DotNetInstanceAdapter)
{
List<MethodBase> candidateMethods = null;
foreach (var member in _classScope.GetMembers(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic))
{
if (this.Name.Equals(member.Name, StringComparison.OrdinalIgnoreCase))
{
var propertyInfo = member as PropertyInfo;
if (propertyInfo != null)
{
var getMethod = propertyInfo.GetGetMethod(nonPublic: true);
var setMethod = propertyInfo.GetSetMethod(nonPublic: true);

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));
}
}
else
{
var fieldInfo = member as FieldInfo;
if (fieldInfo != null)
{
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 || methodInfo.IsFamilyOrAssembly))
{
candidateMethods ??= new List<MethodBase>();

candidateMethods.Add(methodInfo);
}
}
}
}
}

if (candidateMethods != null && candidateMethods.Count > 0)
{
var psMethodInfo = memberInfo as PSMethod;
if (psMethodInfo != null)
{
var cacheEntry = (DotNetAdapter.MethodCacheEntry)psMethodInfo.adapterData;
candidateMethods.AddRange(cacheEntry.methodInformationStructures.Select(static e => e.method));
memberInfo = null;
}

if (memberInfo != null)
{
// Ambiguous, it'd be better to report an error other than "can't find member", but I'm lazy.
memberInfo = null;
}
else
{
DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods);
memberInfo = PSMethod.Create(this.Name, PSObject.DotNetInstanceAdapter, null, method);
}
}
GetPSMemberInfoFromClassScopeMembers(
target,
_classScope.GetMembers(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic),
PSObject.DotNetInstanceAdapter,
ref memberInfo);
}

if (hasInstanceMember)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,15 @@ Describe 'Classes inheritance with protected and protected internal members in b
$c1DefinitionProtectedInternal = @'
public class C1ProtectedInternal
{
protected internal const string Constant = "C1_Constant";

protected internal static string StaticField;
protected internal static string StaticProperty { get; set; }
protected internal static string StaticMethod() { return "C1_StaticMethod"; }

static C1ProtectedInternal() => ResetStatic();
public static void ResetStatic() { StaticField = "C1_StaticField"; StaticProperty = "C1_StaticProperty"; }

protected internal string InstanceField = "C1_InstanceField";
protected internal string InstanceProperty { get; set; } = "C1_InstanceProperty";
protected internal string InstanceMethod() { return "C1_InstanceMethod"; }
Expand Down Expand Up @@ -764,6 +773,17 @@ Describe 'Classes inheritance with protected and protected internal members in b
[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() }

[string]GetConstant() { return [C2ProtectedInternal]::Constant }
[string]GetStaticField() { return [C2ProtectedInternal]::StaticField }
[string]SetStaticField([string]$value) { [C2ProtectedInternal]::StaticField = $value; return [C2ProtectedInternal]::StaticField }
[string]GetStaticProperty() { return [C2ProtectedInternal]::StaticProperty }
[string]SetStaticProperty([string]$value) { [C2ProtectedInternal]::StaticProperty = $value; return [C2ProtectedInternal]::StaticProperty }
[string]CallStaticMethod() { return [C2ProtectedInternal]::StaticMethod() }

[string]GetStaticMemberDynamic([string]$name) { return [C2ProtectedInternal]::$name }
[string]SetStaticMemberDynamic([string]$name, [string]$value) { [C2ProtectedInternal]::$name = $value; return [C2ProtectedInternal]::$name }
[string]CallStaticMemberDynamic([string]$name) { return [C2ProtectedInternal]::$name() }
}

[C2ProtectedInternal]
Expand Down Expand Up @@ -870,6 +890,60 @@ Describe 'Classes inheritance with protected and protected internal members in b
}
}

Context 'Derived class can access static base class members' {

BeforeEach {
$testCases.ForEach({ $_['derivedType'].BaseType::ResetStatic() })
}

It 'can access <accessType> constant base field' -TestCases $testCases {
param($derivedType)
$derivedType::new().GetConstant() | Should -Be 'C1_Constant'
}

It 'can access <accessType> static base field' -TestCases $testCases {
param($derivedType)
$c2 = $derivedType::new()
$c2.GetStaticField() | Should -Be 'C1_StaticField'
$c2.SetStaticField('foo_StaticField') | Should -Be 'foo_StaticField'
}

It 'can access <accessType> static base property' -TestCases $testCases {
param($derivedType)
$c2 = $derivedType::new()
$c2.GetStaticProperty() | Should -Be 'C1_StaticProperty'
$c2.SetStaticProperty('foo_StaticProperty') | Should -Be 'foo_StaticProperty'
}

It 'can call <accessType> static base method' -TestCases $testCases {
param($derivedType)
$derivedType::new().CallStaticMethod() | Should -Be 'C1_StaticMethod'
}
}

Context 'Derived class can access static base class members dynamically' {

BeforeEach {
$testCases.ForEach({ $_['derivedType'].BaseType::ResetStatic() })
}

It 'can access <accessType> base constants, fields, and properties' -TestCases $testCases {
param($derivedType)
$c2 = $derivedType::new()
$c2.GetStaticMemberDynamic('Constant') | Should -Be 'C1_Constant'
$c2.GetStaticMemberDynamic('StaticField') | Should -Be 'C1_StaticField'
$c2.GetStaticMemberDynamic('StaticProperty') | Should -Be 'C1_StaticProperty'
$c2.SetStaticMemberDynamic('StaticField', 'foo1') | Should -Be 'foo1'
$c2.SetStaticMemberDynamic('StaticProperty', 'foo2') | Should -Be 'foo2'
}

It 'can call <accessType> static base method' -TestCases $testCases {
param($derivedType)
$derivedType::new().CallStaticMemberDynamic('StaticMethod') | Should -Be 'C1_StaticMethod'
}
}


Context 'Base class members are not accessible outside class scope' {

BeforeAll {
Expand All @@ -891,9 +965,27 @@ Describe 'Classes inheritance with protected and protected internal members in b
{ $c2.$name() } | Should -Throw -ErrorId 'MethodNotFound'
}
}
$staticTest = {
{ $null = $derivedType::Constant } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
{ $null = $derivedType::StaticField } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
{ $null = $derivedType::StaticProperty } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
{ $derivedType::StaticField = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
{ $derivedType::StaticProperty = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
{ $derivedType::StaticMethod() } | Should -Throw -ErrorId 'MethodNotFound'
foreach ($name in @('Constant', 'StaticField', 'StaticProperty')) {
{ $null = $derivedType::$name } | Should -Throw -ErrorId 'PropertyNotFoundStrict'
}
foreach ($name in @('StaticField', 'StaticProperty')) {
{ $derivedType::$name = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException'
}
foreach ($name in @('StaticMethod')) {
{ $derivedType::$name() } | Should -Throw -ErrorId 'MethodNotFound'
}
}
$c3UnrelatedType = Invoke-Expression @"
class C3Unrelated {
[void]RunInstanceTest([type]`$derivedType) { $instanceTest }
[void]RunStaticTest([type]`$derivedType) { $staticTest }
}
[C3Unrelated]
"@
Expand All @@ -919,5 +1011,16 @@ Describe 'Classes inheritance with protected and protected internal members in b
$c3.RunInstanceTest($derivedType)
}
}

It 'cannot access <accessType> static base members in <scopeType>' -TestCases $negativeTestCases {
param($derivedType, $classScope)
if ($null -eq $classScope) {
$staticTest.Invoke()
}
else {
$c3 = $classScope::new()
$c3.RunStaticTest($derivedType)
}
}
}
}