From f221dd459fa206e048c14090855284d67cf2efe0 Mon Sep 17 00:00:00 2001 From: Matthias Wolf <78562192+mawosoft@users.noreply.github.com> Date: Tue, 13 May 2025 15:31:30 +0000 Subject: [PATCH 1/2] Make inherited protected internal static members accessible in class scope. Follow up for #25245. --- .../engine/runtime/Binding/Binders.cs | 69 +++++++++++- .../scripting.Classes.inheritance.tests.ps1 | 103 ++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 634d8b59650..ebd9493396f 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -5644,6 +5644,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, bool hasTypeTableMember; bool hasInstanceMember; BindingRestrictions versionRestriction; + PSMemberInfo memberInfo = null; lock (this) { versionRestriction = BinderUtils.GetVersionCheck(this, _version); @@ -5657,7 +5658,72 @@ 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))) + { + List candidateMethods = null; + foreach (var member in _classScope.GetMembers(BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic)) + { + 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, PSObject.DotNetStaticAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); + } + } + else if (member is FieldInfo fieldInfo) + { + if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly) + { + memberInfo = new PSProperty(this.Name, PSObject.DotNetStaticAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); + } + } + else if (member is MethodInfo methodInfo) + { + if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly)) + { + candidateMethods ??= new List(); + + 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.DotNetStaticAdapter, null, method); + } + } + } + } + return memberInfo; } canOptimize = false; @@ -5665,7 +5731,6 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, 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; diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index 6b24b8b6ce0..3dd845f257d 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -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"; } @@ -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] @@ -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 constant base field' -TestCases $testCases { + param($derivedType) + $derivedType::new().GetConstant() | Should -Be 'C1_Constant' + } + + It 'can access 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 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 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 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 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 { @@ -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] "@ @@ -919,5 +1011,16 @@ Describe 'Classes inheritance with protected and protected internal members in b $c3.RunInstanceTest($derivedType) } } + + It 'cannot access static base members in ' -TestCases $negativeTestCases { + param($derivedType, $classScope) + if ($null -eq $classScope) { + $staticTest.Invoke() + } + else { + $c3 = $classScope::new() + $c3.RunStaticTest($derivedType) + } + } } } From 5345163b87399a24d367a3d70cf96eecd6e85707 Mon Sep 17 00:00:00 2001 From: Matthias Wolf <78562192+mawosoft@users.noreply.github.com> Date: Tue, 13 May 2025 18:20:26 +0000 Subject: [PATCH 2/2] Eliminate code duplication. --- .../engine/runtime/Binding/Binders.cs | 188 +++++++----------- 1 file changed, 70 insertions(+), 118 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index ebd9493396f..f96f963b027 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -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 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(); + + 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, @@ -5665,62 +5725,11 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, var objType = obj as Type ?? obj?.GetType(); if (objType != null && (objType == _classScope || objType.IsSubclassOf(_classScope))) { - List candidateMethods = null; - foreach (var member in _classScope.GetMembers(BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic)) - { - 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, PSObject.DotNetStaticAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); - } - } - else if (member is FieldInfo fieldInfo) - { - if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly) - { - memberInfo = new PSProperty(this.Name, PSObject.DotNetStaticAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); - } - } - else if (member is MethodInfo methodInfo) - { - if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly)) - { - candidateMethods ??= new List(); - - 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.DotNetStaticAdapter, null, method); - } - } + GetPSMemberInfoFromClassScopeMembers( + target, + _classScope.GetMembers(BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic), + PSObject.DotNetStaticAdapter, + ref memberInfo); } } return memberInfo; @@ -5813,68 +5822,11 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.DotNetInstanceAdapter) { - List 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(); - - 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)